BinderDavid / mendel

Mutation Operators for Haskell Sourcecode
BSD 3-Clause "New" or "Revised" License
0 stars 2 forks source link

Mendel

Haskell-CI

Mendel is a library for introducing faults into Haskell sourcecode, which is also called mutant generation. Its implementation is based on two central libraries:

The ability to inject faults has many use cases, for example:

Inspiration

This library is inspired by previous work:

Their implementation, however, was based on the haskell-src-exts library which is no longer maintained. This means that code which uses modern GHC-specific features can no longer be analyzed using this library. It was therefore necessary to reimplement the mutation logic using the data-structures that GHC uses, which are provided in the ghc-lib-parser library.

Limitations

This library works on the parsed AST, not the renamed or typechecked ASTs. For this reason we can only specify mutations which only require the parsed AST.

Example

A small binary is provided which can be used to test the effect of various mutation operators on real code.

> cabal run mendel -- src/Test/Mendel/Parser.hs
-------------------------------------------------------
BEFORE
-------------------------------------------------------
Parse succeeded
module Test.Mendel.Parser (
        parseModule
    ) where
import GHC.Parser qualified as GHC
import GHC.Parser.Lexer qualified as GHC
import GHC.Data.StringBuffer qualified as GHC
import GHC.Data.FastString qualified as GHC
import GHC.Types.SrcLoc qualified as GHC
import GHC.Utils.Outputable qualified as GHC
import GHC.Hs qualified as GHC
import GHC.Data.EnumSet ( empty )
import GHC.Utils.Error qualified as GHC
import System.Exit ( exitFailure )
parseModule ::
  FilePath -> IO (GHC.Located (GHC.HsModule GHC.GhcPs))
parseModule fp
  = do file <- readFile fp
       let parseResult = runParserModule file
       case parseResult of
         GHC.POk _state res -> pure res
         GHC.PFailed _state
           -> do putStrLn "Parse failed"
                 exitFailure
runParserModule ::
  String -> GHC.ParseResult (GHC.Located (GHC.HsModule GHC.GhcPs))
runParserModule str = runParser parserOpts str GHC.parseModule
parserOpts :: GHC.ParserOpts
parserOpts
  = GHC.mkParserOpts empty diagOpts [] False False False False
diagOpts :: GHC.DiagOpts
diagOpts
  = GHC.DiagOpts
      empty empty False False Nothing GHC.defaultSDocContext
runParser ::
  GHC.ParserOpts -> String -> GHC.P a -> GHC.ParseResult a
runParser opts str parser
  = GHC.unP parser parseState
  where
      filename = "<interactive>"
      location = GHC.mkRealSrcLoc (GHC.mkFastString filename) 1 1
      buffer = GHC.stringToStringBuffer str
      parseState = GHC.initParserState opts buffer location
-------------------------------------------------------
AFTER
-------------------------------------------------------
Parse succeeded
module Test.Mendel.Parser (
        parseModule
    ) where
import GHC.Parser qualified as GHC
import GHC.Parser.Lexer qualified as GHC
import GHC.Data.StringBuffer qualified as GHC
import GHC.Data.FastString qualified as GHC
import GHC.Types.SrcLoc qualified as GHC
import GHC.Utils.Outputable qualified as GHC
import GHC.Hs qualified as GHC
import GHC.Data.EnumSet ( empty )
import GHC.Utils.Error qualified as GHC
import System.Exit ( exitFailure )
parseModule ::
  FilePath -> IO (GHC.Located (GHC.HsModule GHC.GhcPs))
parseModule fp
  = do file <- readFile fp
       let parseResult = runParserModule file
       case parseResult of
         GHC.POk _state res -> pure res
         GHC.PFailed _state
           -> do putStrLn "\"deliaf esraP\""
                 exitFailure
runParserModule ::
  String -> GHC.ParseResult (GHC.Located (GHC.HsModule GHC.GhcPs))
runParserModule str = runParser parserOpts str GHC.parseModule
parserOpts :: GHC.ParserOpts
parserOpts
  = GHC.mkParserOpts empty diagOpts [] False False False False
diagOpts :: GHC.DiagOpts
diagOpts
  = GHC.DiagOpts
      empty empty False False Nothing GHC.defaultSDocContext
runParser ::
  GHC.ParserOpts -> String -> GHC.P a -> GHC.ParseResult a
runParser opts str parser
  = GHC.unP parser parseState
  where
      filename = "\">evitcaretni<\""
      location = GHC.mkRealSrcLoc (GHC.mkFastString filename) 1 1
      buffer = GHC.stringToStringBuffer str
      parseState = GHC.initParserState opts buffer location

Developing

In order to format the source code files you have to install fourmolu. You can then format the files via:

> fourmolu --mode inplace $(git ls-files 'src/**.hs')
> fourmolu --mode inplace $(git ls-files 'app/**.hs')
> fourmolu --mode inplace test/Main.hs