packages feed

crux-llvm-0.8: src/Crux/LLVM/Config.hs

{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module Crux.LLVM.Config where

import           Config.Schema
import           Control.Applicative ( Alternative(..) )
import           Control.Exception ( Exception, displayException, throwIO )
import           Control.Monad ( guard )
import           Control.Monad.State ( liftIO, MonadIO )
import qualified Data.Text as Text
import           System.Directory ( doesDirectoryExist )
import           System.Environment ( getExecutablePath )
import           System.FilePath ( (</>), joinPath, normalise, splitPath, takeDirectory )

import qualified Data.LLVM.BitCode as LLVM

import           Lang.Crucible.LLVM.Intrinsics ( IntrinsicsOptions(..), AbnormalExitBehavior(..) )
import           Lang.Crucible.LLVM.MemModel ( MemOptions(..), IndeterminateLoadBehavior(..), laxPointerMemOptions )
import           Lang.Crucible.LLVM.Translation ( TranslationOptions(..) )

import qualified Crux
import           Paths_crux_llvm ( getDataDir )


--
-- LLVM specific errors
--
data CError =
    ClangError Int String String
  | LLVMParseError LLVM.Error
  | MissingFun String
  | BadFun String {- The function name -}
           Bool   {- Is is the main() function?
                     If so, we can recommend the use of the
                     `supply-main-arguments` option. -}
  | EnvError String
  | NoFiles
    deriving Show

instance Exception CError where
  displayException = ppCError

ppCError :: CError -> String
ppCError err = case err of
    NoFiles                -> "crux-llvm requires at least one input file."
    EnvError msg           -> msg
    BadFun fnName isMain   -> unlines $
                                [ "The '" ++ fnName ++ "' function should have no arguments"] ++
                                [ "Enable `supply-main-arguments` to relax this restriction"
                                | isMain
                                ]
    MissingFun x           -> "Cannot find code for " ++ show x
    LLVMParseError e       -> LLVM.formatError e
    ClangError n sout serr ->
      unlines $ [ "`clang` compilation failed."
                , "*** Exit code: " ++ show n
                , "*** Standard out:"
                ] ++
                [ "   " ++ l | l <- lines sout ] ++
                [ "*** Standard error:" ] ++
                [ "   " ++ l | l <- lines serr ]


throwCError :: MonadIO m => CError -> m b
throwCError e = liftIO (throwIO e)


abnormalExitBehaviorSpec :: ValueSpec AbnormalExitBehavior
abnormalExitBehaviorSpec =
  (AlwaysFail <$ atomSpec "always-fail") <!>
  (OnlyAssertFail <$ atomSpec "only-assert-fail") <!>
  (NeverFail <$ atomSpec "never-fail")

-- | What sort of @main@ functions should @crux-llvm@ support simulating?
data SupplyMainArguments
  = NoArguments
    -- ^ Only support simulating @main@ functions with the signature
    --   @int main()@. Attempting to simulate a @main@ function that
    --   takes arguments will result in an error. This is @crux-llvm@'s default
    --   behavior.

  | EmptyArguments
    -- ^ Support simulating both @int main()@ and
    --   @int(main argc, char *argv[])@. If simulating the latter, supply the
    --   arguments @argc=0@ and @argv={}@. This is a reasonable choice for
    --   programs whose @main@ function is declared with the latter signature
    --   but never make use of @argc@ or @argv@.
  deriving Show

supplyMainArgumentsSpec :: ValueSpec SupplyMainArguments
supplyMainArgumentsSpec =
  (NoArguments <$ atomSpec "none") <!>
  (EmptyArguments <$ atomSpec "empty")

indeterminateLoadBehaviorSpec :: ValueSpec IndeterminateLoadBehavior
indeterminateLoadBehaviorSpec =
  (StableSymbolic <$ atomSpec "stable-symbolic") <!>
  (UnstableSymbolic <$ atomSpec "unstable-symbolic")

data LLVMOptions = LLVMOptions
  { clangBin   :: FilePath
  , linkBin    :: FilePath
  , clangOpts  :: [String]
  , libDir     :: FilePath
  , incDirs    :: [FilePath]
  , targetArch :: Maybe String
  , ubSanitizers :: [String]
  , intrinsicsOpts :: IntrinsicsOptions
  , memOpts        :: MemOptions
  , transOpts      :: TranslationOptions
  , entryPoint :: String
  , lazyCompile :: Bool
  , noCompile :: Bool
  , optLevel :: Int
  , symFSRoot :: Maybe FilePath
  , supplyMainArguments :: SupplyMainArguments
  }

-- | The @c-src@ directory, which contains @crux-llvm@–specific files such as
-- @crucible.h@, can live in different locations depending on how @crux-llvm@
-- was built. This function looks in a couple of common places:
--
-- 1. A directory relative to the @crux-llvm@ binary itself. This is the case
--    when running a @crux-llvm@ binary distribution. If that can't be found,
--    default to...
--
-- 2. The @data-files@ directory, as reported by @cabal@'s 'getDataDir'
--    function.
--
-- This isn't always guaranteed to work in every situation, but it should cover
-- enough common cases to be useful in practice.
findDefaultLibDir :: IO FilePath
findDefaultLibDir = getDirRelativeToBin <|> getCabalDataDir
  where
    getDirRelativeToBin :: IO FilePath
    getDirRelativeToBin = do
      binDir <- takeDirectory `fmap` getExecutablePath
      let libDirPrefix = normalise . joinPath . init . splitPath $ binDir
          libDir       = libDirPrefix </> cSrc
      libDirExists <- doesDirectoryExist libDir
      guard libDirExists
      pure libDir

    getCabalDataDir :: IO FilePath
    getCabalDataDir = do
      libDirPrefix <- getDataDir
      pure $ libDirPrefix </> cSrc

    cSrc :: FilePath
    cSrc = "c-src"

llvmCruxConfig :: IO (Crux.Config LLVMOptions)
llvmCruxConfig = do
  libDirDefault <- findDefaultLibDir
  return Crux.Config
   { Crux.cfgFile =
      do clangBin <- Crux.section "clang" Crux.fileSpec "clang"
                     "Binary to use for `clang`."

         linkBin  <- Crux.section "llvm-link" Crux.fileSpec "llvm-link"
                     "Binary to use for `llvm-link`."

         clangOpts <- Crux.section "clang-opts"
                                    (Crux.oneOrList Crux.stringSpec) []
                      "Additional options for `clang`."

         libDir <- Crux.section "lib-dir" Crux.dirSpec libDirDefault
                   "Locations of `crux-llvm` support library."

         incDirs <- Crux.section "include-dirs"
                        (Crux.oneOrList Crux.dirSpec) []
                    "Additional include directories."

         targetArch <- Crux.sectionMaybe "target-architecture" Crux.stringSpec
                       "Target architecture to pass to LLVM build operations.\
                       \ Default is no specification for current system architecture"

         intrinsicsOpts <- do abnormalExitBehavior <-
                                Crux.section "abnormal-exit-behavior" abnormalExitBehaviorSpec AlwaysFail
                                  (Text.unwords
                                    [ "Should Crux fail when simulating a function which triggers an"
                                    , "abnormal exit, such as abort()? Possible options are:"
                                    , "'always-fail' (default); 'only-assert-fail', which only fails"
                                    , "when simulating __assert_fail(); and 'never-fail'."
                                    , "The latter two options are primarily useful for SV-COMP."
                                    ])
                              return IntrinsicsOptions{..}

         memOpts <- do laxPointerOrdering <-
                         Crux.section "lax-pointer-ordering" Crux.yesOrNoSpec False
                           "Allow order comparisons between pointers from different allocation blocks"
                       laxConstantEquality <-
                         Crux.section "lax-constant-equality" Crux.yesOrNoSpec False
                           "Allow equality comparisons between pointers to constant data"
                       laxLoadsAndStores <-
                         Crux.section "lax-loads-and-stores" Crux.yesOrNoSpec False
                           "Relax some of Crucible's validity checks for memory loads and stores"
                       indeterminateLoadBehavior <-
                         Crux.section "indeterminate-load-behavior" indeterminateLoadBehaviorSpec StableSymbolic
                           (Text.unwords
                             [ "(Only takes effect is 'lax-loads-and-stores' is enabled.)"
                             , "What should the semantics of reading from uninitialized"
                             , "memory be? Possible options are:"
                             , "'stable-symbolic' (default), which causes all allocations to"
                             , "be initialized with a fresh symbolic value; and"
                             , "'unstable-symbolic', which causes each read from uninitialized"
                             , "memory to return a fresh symbolic value."
                             ])
                       return MemOptions{..}

         transOpts <- do laxArith <-
                           Crux.section "lax-arithmetic" Crux.yesOrNoSpec False
                             "Do not produce proof obligations related to arithmetic overflow, etc."
                         optLoopMerge <-
                           Crux.section "opt-loop-merge" Crux.yesOrNoSpec False
                             "Insert merge blocks in loops with early exits (i.e. breaks or returns). This may improve simulation performance."
                         debugIntrinsics <- Crux.section "debug-intrinsics" Crux.yesOrNoSpec False
                              "Translate statements using certain llvm.dbg intrinsic functions."
                         return TranslationOptions{..}

         entryPoint <- Crux.section "entry-point" Crux.stringSpec "main"
                           "Name of the entry point function to begin simulation."

         lazyCompile <- Crux.section "lazy-compile" Crux.yesOrNoSpec False
                           "Avoid compiling bitcode from source if intermediate files already exist"

         noCompile <- Crux.section "no-compile" Crux.yesOrNoSpec False
                        "Treat the input file as an LLVM module, do not compile it"

         ubSanitizers <- Crux.section "ub-sanitizers" (Crux.listSpec Crux.stringSpec) []
                           "Undefined Behavior sanitizers to enable in `clang`"

         optLevel <- Crux.section "opt-level" Crux.numSpec 1
                           "Optimization level to request from `clang`"

         symFSRoot <- Crux.sectionMaybe "symbolic-fs-root" Crux.stringSpec
                      "The root of a symbolic filesystem to make available to\
                      \ the program during symbolic execution"

         supplyMainArguments <-
           Crux.section "supply-main-arguments" supplyMainArgumentsSpec NoArguments
             (Text.unwords
               [ "One of `none` or `empty` (default: none). If `none`, only"
               , "support simulating `main` entrypoint functions with the"
               , "signature `int main()`. If `empty`, also support simulating"
               , "`int main(int argc, char *argv[])` such that argc=0 and"
               , "argv={} are chosen as the arguments."
               ])

         return LLVMOptions { .. }

   , Crux.cfgEnv  =
      [ Crux.EnvVar "CLANG"      "Binary to use for `clang`."
        $ \v opts -> Right opts { clangBin = v }

      , Crux.EnvVar "CLANG_OPTS" "Options to pass to `clang`."
        $ \v opts -> Right opts { clangOpts = words v }

      , Crux.EnvVar "LLVM_LINK" "Use this binary to link LLVM bitcode (`llvm-link`)."
        $ \v opts -> Right opts { linkBin = v }

      ]

   , Crux.cfgCmdLineFlag =
      [ Crux.Option ['I'] ["include-dirs"]
        "Additional include directories."
        $ Crux.ReqArg "DIR"
        $ \d opts -> Right opts { incDirs = d : incDirs opts }

      , Crux.Option [] ["target"]
        "Target architecture to pass to LLVM build operations"
        $ Crux.OptArg "ARCH"
        $ \a opts -> Right opts { targetArch = a }

      , Crux.Option [] ["lax-pointers"]
        "Turn on lax rules for pointer comparisons"
        $ Crux.NoArg
        $ \opts -> Right opts{ memOpts = laxPointerMemOptions }

      , Crux.Option [] ["lax-arithmetic"]
        "Turn on lax rules for arithemetic overflow"
        $ Crux.NoArg
        $ \opts -> Right opts { transOpts = (transOpts opts) { laxArith = True } }

      , Crux.Option [] ["opt-loop-merge"]
        "Insert merge blocks in loops with early exits"
        $ Crux.NoArg
        $ \opts -> Right opts { transOpts = (transOpts opts) { optLoopMerge = True } }

      , Crux.Option [] ["lazy-compile"]
        "Avoid compiling bitcode from source if intermediate files already exist (default: off)"
        $ Crux.NoArg
        $ \opts -> Right opts{ lazyCompile = True }

      , Crux.Option [] ["no-compile"]
        "Treat the input file as an LLVM module, do not compile it"
        $ Crux.NoArg
        $ \opts -> Right opts{ noCompile = True }

      , Crux.Option [] ["entry-point"]
        "Name of the entry point to begin simulation"
        $ Crux.ReqArg "SYMBOL"
        $ \s opts -> Right opts{ entryPoint = s }

      , Crux.Option "O" []
        "Optimization level for `clang`"
        $ Crux.ReqArg "NUM"
        $ Crux.parsePosNum "NUM"
        $ \v opts -> opts { optLevel = v }

      , Crux.Option [] ["symbolic-fs-root"]
        "The root of a symbolic filesystem to make available to the program during symbolic execution"
        $ Crux.OptArg "DIR"
        $ \a opts -> Right opts { symFSRoot = a }
      ]
   }