packages feed

llvm-analysis-0.3.0: tests/ClassHierarchyTests.hs

module Main ( main ) where

import Data.Generics.Uniplate.Data
import Data.List ( find )
import Data.Map ( Map )
import qualified Data.Map as M
import Data.Maybe ( mapMaybe )
import Data.Set ( Set )
import qualified Data.Set as S
import System.Environment ( getArgs, withArgs )
import System.FilePath ( (<.>) )
import Test.HUnit ( assertEqual )

import qualified ABI.Itanium as ABI

import LLVM.Analysis
import LLVM.Analysis.ClassHierarchy
import LLVM.Analysis.Util.Names
import LLVM.Analysis.Util.Testing
import LLVM.Parse

main :: IO ()
main = do
  args <- getArgs
  let pattern1 = case args of
        [] -> "tests/class-hierarchy/*.cpp"
        [infile] -> infile
        _ -> error "Only one argument allowed"
      pattern2 = case args of
        [] -> "tests/virtual-dispatch/*.cpp"
        [infile] -> infile
        _ -> error "Only one argument allowed"
      testDescriptors = [ TestDescriptor { testPattern = pattern1
                                         , testExpectedMapping = (<.> "expected")
                                         , testResultBuilder = analyzeHierarchy
                                         , testResultComparator = assertEqual
                                         }
                        , TestDescriptor { testPattern = pattern2
                                         , testExpectedMapping = (<.> "expected")
                                         , testResultBuilder = findCallees
                                         , testResultComparator = assertEqual
                                         }
                        ]
  withArgs [] $ testAgainstExpected opts parser testDescriptors
  where
    opts = [ "-mem2reg", "-basicaa", "-gvn" ]
    parser = parseLLVMFile defaultParserOptions

analyzeHierarchy :: Module -> Map String (Set String)
analyzeHierarchy = classHierarchyToTestFormat . runCHA

findCallees :: Module -> Map String (Set String)
findCallees m = M.fromList $ mapMaybe (firstCalleeTargets cha) funcs
  where
    cha = runCHA m
    funcs = moduleDefinedFunctions m

functionToDemangledName :: Function -> String
functionToDemangledName f =
  case parseFunctionName f of
    Left e -> error e
    Right sname ->
      case unparseFunctionName sname of
        Nothing -> error ("Unable to unparse function name: " ++ show sname)
        Just n -> n

firstCalleeTargets :: CHA -> Function -> Maybe (String, Set String)
firstCalleeTargets cha f = do
  case isConstructor f || isVirtualThunk f of
    True -> Nothing
    False -> do
      firstCall <- find isCallInst insts
      callees <- resolveVirtualCallee cha firstCall
      return (fname, S.fromList (map functionToDemangledName callees))
  where
    insts = functionInstructions f
    fname = functionToDemangledName f

isVirtualThunk :: Function -> Bool
isVirtualThunk f =
  case dname of
    Left _ -> False
    Right sname ->
      case sname of
        ABI.OverrideThunk _ _ -> True
        ABI.OverrideThunkCovariant _ _ _ -> True
        _ -> False
  where
    n = identifierAsString (functionName f)
    dname = ABI.demangleName n

isConstructor :: Function -> Bool
isConstructor f =
  case dname of
    Left _ -> False
    Right structuredName ->
      case universeBi structuredName of
        [ABI.C2] -> True
        [ABI.C1] -> True
        [ABI.C3] -> True
        _ -> False
  where
    n = identifierAsString (functionName f)
    dname = ABI.demangleName n

isCallInst :: Instruction -> Bool
isCallInst i =
  case i of
    CallInst {} -> True
    _ -> False