packages feed

hic-0.0.0.1: test/Language/Cimple/Analysis/CallGraphAnalysisSpec.hs

{-# LANGUAGE OverloadedStrings #-}
module Language.Cimple.Analysis.CallGraphAnalysisSpec (spec) where

import           Data.List                                  (sort)
import qualified Data.Map.Strict                            as Map
import qualified Data.Set                                   as Set
import           Language.Cimple.Analysis.CallGraphAnalysis
import           Language.Cimple.Hic.InferenceSpec          (mustParse)
import           Test.Hspec

spec :: Spec
spec = describe "Language.Cimple.Analysis.CallGraphAnalysis" $ do
    it "identifies direct calls" $ do
        prog <- mustParse
            [ "void my_g();"
            , "void my_f() { my_g(); }"
            ]
        let res = runCallGraphAnalysis prog
        Map.lookup "my_f" (cgrDirectCalls res) `shouldBe` Just (Set.singleton "my_g")

    it "identifies self-recursion" $ do
        prog <- mustParse ["void my_f() { my_f(); }"]
        let res = runCallGraphAnalysis prog
        Map.lookup "my_f" (cgrDirectCalls res) `shouldBe` Just (Set.singleton "my_f")
        cgrSccs res `shouldBe` [Cyclic ["my_f"]]

    it "identifies mutual recursion" $ do
        prog <- mustParse
            [ "void my_g();"
            , "void my_f() { my_g(); }"
            , "void my_g() { my_f(); }"
            ]
        let res = runCallGraphAnalysis prog
        -- SCCs are returned in reverse topological order, but for a cycle it's one SCC
        case cgrSccs res of
            [Cyclic nodes] -> sort nodes `shouldBe` ["my_f", "my_g"]
            _              -> expectationFailure $ "Expected one Cyclic SCC, got: " ++ show (cgrSccs res)

    it "identifies multiple callers" $ do
        prog <- mustParse
            [ "void my_h();"
            , "void my_f() { my_h(); }"
            , "void my_g() { my_h(); }"
            ]
        let res = runCallGraphAnalysis prog
        Map.lookup "my_f" (cgrDirectCalls res) `shouldBe` Just (Set.singleton "my_h")
        Map.lookup "my_g" (cgrDirectCalls res) `shouldBe` Just (Set.singleton "my_h")

    it "handles nested calls" $ do
        prog <- mustParse
            [ "void my_h();"
            , "void my_g() { my_h(); }"
            , "void my_f() { my_g(); }"
            ]
        let res = runCallGraphAnalysis prog
        -- Data.Graph.stronglyConnComp returns SCCs in reverse topological order.
        -- So leaf should come first.
        cgrSccs res `shouldBe` [Acyclic "my_h", Acyclic "my_g", Acyclic "my_f"]

    it "ignores function pointer calls (for now)" $ do
        prog <- mustParse
            [ "typedef void my_ptr_cb();"
            , "void my_f(my_ptr_cb *my_ptr) { my_ptr(); }"
            ]
        let res = runCallGraphAnalysis prog
        Map.lookup "my_f" (cgrDirectCalls res) `shouldBe` Just Set.empty

    it "identifies calls in initializers" $ do
        prog <- mustParse ["int g(); void f() { int x = g(); }"]
        let res = runCallGraphAnalysis prog
        Map.lookup "f" (cgrDirectCalls res) `shouldBe` Just (Set.singleton "g")

    it "handles multiple calls to the same function" $ do
        prog <- mustParse ["void g(); void f() { g(); g(); }"]
        let res = runCallGraphAnalysis prog
        Map.lookup "f" (cgrDirectCalls res) `shouldBe` Just (Set.singleton "g")