mirror of
https://github.com/github/semantic.git
synced 2024-12-20 05:11:44 +03:00
Merge pull request #1520 from github/call-graph-analysis
Call graph analysis
This commit is contained in:
commit
8701de3af0
@ -20,6 +20,7 @@ library
|
|||||||
, Analysis.Abstract.Dead
|
, Analysis.Abstract.Dead
|
||||||
, Analysis.Abstract.Evaluating
|
, Analysis.Abstract.Evaluating
|
||||||
, Analysis.Abstract.Tracing
|
, Analysis.Abstract.Tracing
|
||||||
|
, Analysis.CallGraph
|
||||||
, Analysis.ConstructorName
|
, Analysis.ConstructorName
|
||||||
, Analysis.CyclomaticComplexity
|
, Analysis.CyclomaticComplexity
|
||||||
, Analysis.Decorator
|
, Analysis.Decorator
|
||||||
|
109
src/Analysis/CallGraph.hs
Normal file
109
src/Analysis/CallGraph.hs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
{-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving, MultiParamTypeClasses, ScopedTypeVariables, TypeFamilies, UndecidableInstances #-}
|
||||||
|
module Analysis.CallGraph
|
||||||
|
( CallGraph(..)
|
||||||
|
, renderCallGraph
|
||||||
|
, buildCallGraph
|
||||||
|
, CallGraphAlgebra(..)
|
||||||
|
) where
|
||||||
|
|
||||||
|
import qualified Algebra.Graph as G
|
||||||
|
import Algebra.Graph.Class
|
||||||
|
import Algebra.Graph.Export.Dot
|
||||||
|
import Data.Abstract.FreeVariables
|
||||||
|
import Data.Set (member)
|
||||||
|
import qualified Data.Syntax as Syntax
|
||||||
|
import qualified Data.Syntax.Declaration as Declaration
|
||||||
|
import Data.Term
|
||||||
|
import Prologue hiding (empty)
|
||||||
|
|
||||||
|
-- | The graph of function definitions to symbols used in a given program.
|
||||||
|
newtype CallGraph = CallGraph { unCallGraph :: G.Graph Name }
|
||||||
|
deriving (Eq, Graph, Show)
|
||||||
|
|
||||||
|
-- | Build the 'CallGraph' for a 'Term' recursively.
|
||||||
|
buildCallGraph :: (CallGraphAlgebra syntax, Foldable syntax, FreeVariables1 syntax, Functor syntax) => Term syntax ann -> Set Name -> CallGraph
|
||||||
|
buildCallGraph = foldSubterms callGraphAlgebra
|
||||||
|
|
||||||
|
|
||||||
|
-- | Render a 'CallGraph' to a 'ByteString' in DOT notation.
|
||||||
|
renderCallGraph :: CallGraph -> ByteString
|
||||||
|
renderCallGraph = export (defaultStyle friendlyName) . unCallGraph
|
||||||
|
|
||||||
|
|
||||||
|
-- | Types which contribute to a 'CallGraph'. There is exactly one instance of this typeclass; customizing the 'CallGraph's for a new type is done by defining an instance of 'CustomCallGraphAlgebra' instead.
|
||||||
|
--
|
||||||
|
-- This typeclass employs the Advanced Overlap techniques designed by Oleg Kiselyov & Simon Peyton Jones: https://wiki.haskell.org/GHC/AdvancedOverlap.
|
||||||
|
class CallGraphAlgebra syntax where
|
||||||
|
-- | A 'SubtermAlgebra' computing the 'CallGraph' for a piece of @syntax@.
|
||||||
|
callGraphAlgebra :: FreeVariables term => syntax (Subterm term (Set Name -> CallGraph)) -> Set Name -> CallGraph
|
||||||
|
|
||||||
|
instance (CallGraphAlgebraStrategy syntax ~ strategy, CallGraphAlgebraWithStrategy strategy syntax) => CallGraphAlgebra syntax where
|
||||||
|
callGraphAlgebra = callGraphAlgebraWithStrategy (Proxy :: Proxy strategy)
|
||||||
|
|
||||||
|
|
||||||
|
-- | Types whose contribution to a 'CallGraph' is customized. If an instance’s definition is not being used, ensure that the type is mapped to 'Custom' in the 'CallGraphAlgebraStrategy'.
|
||||||
|
class CustomCallGraphAlgebra syntax where
|
||||||
|
customCallGraphAlgebra :: FreeVariables term => syntax (Subterm term (Set Name -> CallGraph)) -> Set Name -> CallGraph
|
||||||
|
|
||||||
|
-- | 'Declaration.Function's produce a vertex for their name, with edges to any free variables in their body.
|
||||||
|
instance CustomCallGraphAlgebra Declaration.Function where
|
||||||
|
customCallGraphAlgebra Declaration.Function{..} bound = foldMap vertex (freeVariables (subterm functionName)) `connect` subtermValue functionBody (foldMap (freeVariables . subterm) functionParameters <> bound)
|
||||||
|
|
||||||
|
-- | 'Declaration.Method's produce a vertex for their name, with edges to any free variables in their body.
|
||||||
|
instance CustomCallGraphAlgebra Declaration.Method where
|
||||||
|
customCallGraphAlgebra Declaration.Method{..} bound = foldMap vertex (freeVariables (subterm methodName)) `connect` subtermValue methodBody (foldMap (freeVariables . subterm) methodParameters <> bound)
|
||||||
|
|
||||||
|
-- | 'Syntax.Identifier's produce a vertex iff it’s unbound in the 'Set'.
|
||||||
|
instance CustomCallGraphAlgebra Syntax.Identifier where
|
||||||
|
customCallGraphAlgebra (Syntax.Identifier name) bound
|
||||||
|
| name `member` bound = empty
|
||||||
|
| otherwise = vertex name
|
||||||
|
|
||||||
|
instance Apply CallGraphAlgebra syntaxes => CustomCallGraphAlgebra (Union syntaxes) where
|
||||||
|
customCallGraphAlgebra = Prologue.apply (Proxy :: Proxy CallGraphAlgebra) callGraphAlgebra
|
||||||
|
|
||||||
|
instance CallGraphAlgebra syntax => CustomCallGraphAlgebra (TermF syntax a) where
|
||||||
|
customCallGraphAlgebra = callGraphAlgebra . termFOut
|
||||||
|
|
||||||
|
|
||||||
|
-- | The mechanism selecting 'Default'/'Custom' implementations for 'callGraphAlgebra' depending on the @syntax@ type.
|
||||||
|
class CallGraphAlgebraWithStrategy (strategy :: Strategy) syntax where
|
||||||
|
callGraphAlgebraWithStrategy :: FreeVariables term => proxy strategy -> syntax (Subterm term (Set Name -> CallGraph)) -> Set Name -> CallGraph
|
||||||
|
|
||||||
|
-- | The 'Default' definition of 'callGraphAlgebra' combines all of the 'CallGraph's within the @syntax@ 'Monoid'ally.
|
||||||
|
instance Foldable syntax => CallGraphAlgebraWithStrategy 'Default syntax where
|
||||||
|
callGraphAlgebraWithStrategy _ = foldMap subtermValue
|
||||||
|
|
||||||
|
-- | The 'Custom' strategy calls out to the 'customCallGraphAlgebra' method.
|
||||||
|
instance CustomCallGraphAlgebra syntax => CallGraphAlgebraWithStrategy 'Custom syntax where
|
||||||
|
callGraphAlgebraWithStrategy _ = customCallGraphAlgebra
|
||||||
|
|
||||||
|
|
||||||
|
-- | Which instance of 'CustomCallGraphAlgebra' to use for a given @syntax@ type.
|
||||||
|
data Strategy = Default | Custom
|
||||||
|
|
||||||
|
-- | A mapping of @syntax@ types onto 'Strategy's.
|
||||||
|
type family CallGraphAlgebraStrategy syntax where
|
||||||
|
CallGraphAlgebraStrategy Declaration.Function = 'Custom
|
||||||
|
CallGraphAlgebraStrategy Declaration.Method = 'Custom
|
||||||
|
CallGraphAlgebraStrategy Syntax.Identifier = 'Custom
|
||||||
|
CallGraphAlgebraStrategy (Union fs) = 'Custom
|
||||||
|
CallGraphAlgebraStrategy (TermF f a) = 'Custom
|
||||||
|
CallGraphAlgebraStrategy a = 'Default
|
||||||
|
|
||||||
|
|
||||||
|
instance Monoid CallGraph where
|
||||||
|
mempty = empty
|
||||||
|
mappend = overlay
|
||||||
|
|
||||||
|
instance Ord CallGraph where
|
||||||
|
compare (CallGraph G.Empty) (CallGraph G.Empty) = EQ
|
||||||
|
compare (CallGraph G.Empty) _ = LT
|
||||||
|
compare _ (CallGraph G.Empty) = GT
|
||||||
|
compare (CallGraph (G.Vertex a)) (CallGraph (G.Vertex b)) = compare a b
|
||||||
|
compare (CallGraph (G.Vertex _)) _ = LT
|
||||||
|
compare _ (CallGraph (G.Vertex _)) = GT
|
||||||
|
compare (CallGraph (G.Overlay a1 a2)) (CallGraph (G.Overlay b1 b2)) = (compare `on` CallGraph) a1 b1 <> (compare `on` CallGraph) a2 b2
|
||||||
|
compare (CallGraph (G.Overlay _ _)) _ = LT
|
||||||
|
compare _ (CallGraph (G.Overlay _ _)) = GT
|
||||||
|
compare (CallGraph (G.Connect a1 a2)) (CallGraph (G.Connect b1 b2)) = (compare `on` CallGraph) a1 b1 <> (compare `on` CallGraph) a2 b2
|
@ -10,7 +10,6 @@ import Data.Abstract.FreeVariables
|
|||||||
import Data.Abstract.Store
|
import Data.Abstract.Store
|
||||||
import Data.Abstract.Value
|
import Data.Abstract.Value
|
||||||
import Data.Foldable (asum, toList)
|
import Data.Foldable (asum, toList)
|
||||||
import Data.Semigroup
|
|
||||||
import Data.Semigroup.Reducer
|
import Data.Semigroup.Reducer
|
||||||
import Prelude hiding (fail)
|
import Prelude hiding (fail)
|
||||||
|
|
||||||
@ -20,34 +19,13 @@ class (Monad m, Ord l, l ~ LocationFor value, Reducer value (Cell l value)) => M
|
|||||||
|
|
||||||
alloc :: Name -> m (Address l value)
|
alloc :: Name -> m (Address l value)
|
||||||
|
|
||||||
-- | Look up or allocate an address for a 'Name' free in a given term & assign it a given value, returning the 'Name' paired with the address.
|
-- | Look up or allocate an address for a 'Name'.
|
||||||
--
|
lookupOrAlloc :: ( MonadAddressable (LocationFor value) value m
|
||||||
-- The term is expected to contain one and only one free 'Name', meaning that care should be taken to apply this only to e.g. identifiers.
|
, MonadEnvironment value m
|
||||||
lookupOrAlloc :: ( FreeVariables term
|
|
||||||
, MonadAddressable (LocationFor value) value m
|
|
||||||
, MonadStore value m
|
|
||||||
, Semigroup (CellFor value)
|
|
||||||
)
|
)
|
||||||
=> term
|
=> Name
|
||||||
-> value
|
-> m (Address (LocationFor value) value)
|
||||||
-> EnvironmentFor value
|
lookupOrAlloc name = lookupLocalEnv name >>= maybe (alloc name) pure
|
||||||
-> m (Name, Address (LocationFor value) value)
|
|
||||||
lookupOrAlloc term = let [name] = toList (freeVariables term) in
|
|
||||||
lookupOrAlloc' name
|
|
||||||
|
|
||||||
-- | Look up or allocate an address for a 'Name' & assign it a given value, returning the 'Name' paired with the address.
|
|
||||||
lookupOrAlloc' :: ( Semigroup (CellFor value)
|
|
||||||
, MonadAddressable (LocationFor value) value m
|
|
||||||
, MonadStore value m
|
|
||||||
)
|
|
||||||
=> Name
|
|
||||||
-> value
|
|
||||||
-> EnvironmentFor value
|
|
||||||
-> m (Name, Address (LocationFor value) value)
|
|
||||||
lookupOrAlloc' name v env = do
|
|
||||||
a <- maybe (alloc name) pure (envLookup name env)
|
|
||||||
assign a v
|
|
||||||
pure (name, a)
|
|
||||||
|
|
||||||
|
|
||||||
letrec :: ( MonadAddressable (LocationFor value) value m
|
letrec :: ( MonadAddressable (LocationFor value) value m
|
||||||
@ -58,7 +36,7 @@ letrec :: ( MonadAddressable (LocationFor value) value m
|
|||||||
-> m value
|
-> m value
|
||||||
-> m (value, Address (LocationFor value) value)
|
-> m (value, Address (LocationFor value) value)
|
||||||
letrec name body = do
|
letrec name body = do
|
||||||
addr <- alloc name
|
addr <- lookupOrAlloc name
|
||||||
v <- localEnv (envInsert name addr) body
|
v <- localEnv (envInsert name addr) body
|
||||||
assign addr v
|
assign addr v
|
||||||
pure (v, addr)
|
pure (v, addr)
|
||||||
|
@ -12,6 +12,7 @@ module Control.Abstract.Evaluator
|
|||||||
|
|
||||||
import Data.Abstract.Address
|
import Data.Abstract.Address
|
||||||
import Data.Abstract.Configuration
|
import Data.Abstract.Configuration
|
||||||
|
import Data.Abstract.Environment
|
||||||
import Data.Abstract.FreeVariables
|
import Data.Abstract.FreeVariables
|
||||||
import Data.Abstract.ModuleTable
|
import Data.Abstract.ModuleTable
|
||||||
import Data.Abstract.Store
|
import Data.Abstract.Store
|
||||||
@ -55,6 +56,16 @@ class Monad m => MonadEnvironment value m | m -> value where
|
|||||||
-- | Run an action with a locally-modified environment.
|
-- | Run an action with a locally-modified environment.
|
||||||
localEnv :: (EnvironmentFor value -> EnvironmentFor value) -> m a -> m a
|
localEnv :: (EnvironmentFor value -> EnvironmentFor value) -> m a -> m a
|
||||||
|
|
||||||
|
-- | Look a 'Name' up in the local environment.
|
||||||
|
lookupLocalEnv :: Name -> m (Maybe (Address (LocationFor value) value))
|
||||||
|
lookupLocalEnv name = envLookup name <$> askLocalEnv
|
||||||
|
|
||||||
|
-- | Look up a 'Name' in the local environment, running an action with the resolved address (if any).
|
||||||
|
lookupWith :: (Address (LocationFor value) value -> m value) -> Name -> m (Maybe value)
|
||||||
|
lookupWith with name = do
|
||||||
|
addr <- lookupLocalEnv name
|
||||||
|
maybe (pure Nothing) (fmap Just . with) addr
|
||||||
|
|
||||||
-- | Update the global environment.
|
-- | Update the global environment.
|
||||||
modifyGlobalEnv :: MonadEnvironment value m => (EnvironmentFor value -> EnvironmentFor value) -> m ()
|
modifyGlobalEnv :: MonadEnvironment value m => (EnvironmentFor value -> EnvironmentFor value) -> m ()
|
||||||
modifyGlobalEnv f = do
|
modifyGlobalEnv f = do
|
||||||
|
@ -40,7 +40,7 @@ bindExports aliases env = Environment pairs
|
|||||||
--
|
--
|
||||||
-- Unbound names are silently dropped.
|
-- Unbound names are silently dropped.
|
||||||
envRoots :: (Ord l, Foldable t) => Environment l a -> t Name -> Live l a
|
envRoots :: (Ord l, Foldable t) => Environment l a -> t Name -> Live l a
|
||||||
envRoots env = foldr ((<>) . maybe mempty liveSingleton . flip envLookup env) mempty
|
envRoots env = foldMap (maybe mempty liveSingleton . flip envLookup env)
|
||||||
|
|
||||||
envAll :: (Ord l) => Environment l a -> Live l a
|
envAll :: (Ord l) => Environment l a -> Live l a
|
||||||
envAll (Environment env) = Live $ Set.fromList (Map.elems env)
|
envAll (Environment env) = Live $ Set.fromList (Map.elems env)
|
||||||
|
@ -144,9 +144,9 @@ type CellFor value = Cell (LocationFor value) value
|
|||||||
type LiveFor value = Live (LocationFor value) value
|
type LiveFor value = Live (LocationFor value) value
|
||||||
|
|
||||||
-- | The location type (the body of 'Address'es) which should be used for an abstract value type.
|
-- | The location type (the body of 'Address'es) which should be used for an abstract value type.
|
||||||
type family LocationFor value :: * where
|
type family LocationFor value :: *
|
||||||
LocationFor (Value location term) = location
|
type instance LocationFor (Value location term) = location
|
||||||
LocationFor Type.Type = Monovariant
|
type instance LocationFor Type.Type = Monovariant
|
||||||
|
|
||||||
-- | Value types, e.g. closures, which can root a set of addresses.
|
-- | Value types, e.g. closures, which can root a set of addresses.
|
||||||
class ValueRoots value where
|
class ValueRoots value where
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
module Data.Syntax where
|
module Data.Syntax where
|
||||||
|
|
||||||
import Control.Monad.Fail
|
import Control.Monad.Fail
|
||||||
import Data.Abstract.Environment
|
|
||||||
import Data.Abstract.Evaluatable
|
import Data.Abstract.Evaluatable
|
||||||
import Data.AST
|
import Data.AST
|
||||||
import Data.Range
|
import Data.Range
|
||||||
@ -108,9 +107,7 @@ instance Ord1 Identifier where liftCompare = genericLiftCompare
|
|||||||
instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec
|
instance Show1 Identifier where liftShowsPrec = genericLiftShowsPrec
|
||||||
|
|
||||||
instance Evaluatable Identifier where
|
instance Evaluatable Identifier where
|
||||||
eval (Identifier name) = do
|
eval (Identifier name) = lookupWith deref name >>= maybe (fail ("free variable: " <> show name)) pure
|
||||||
env <- askLocalEnv
|
|
||||||
maybe (fail ("free variable: " <> show name)) deref (envLookup name env)
|
|
||||||
|
|
||||||
instance FreeVariables1 Identifier where
|
instance FreeVariables1 Identifier where
|
||||||
liftFreeVariables _ (Identifier x) = Set.singleton x
|
liftFreeVariables _ (Identifier x) = Set.singleton x
|
||||||
|
@ -94,10 +94,11 @@ instance Show1 Assignment where liftShowsPrec = genericLiftShowsPrec
|
|||||||
instance Evaluatable Assignment where
|
instance Evaluatable Assignment where
|
||||||
eval Assignment{..} = do
|
eval Assignment{..} = do
|
||||||
v <- subtermValue assignmentValue
|
v <- subtermValue assignmentValue
|
||||||
(var, a) <- askLocalEnv >>= lookupOrAlloc (subterm assignmentTarget) v
|
addr <- lookupOrAlloc name
|
||||||
|
assign addr v
|
||||||
modifyGlobalEnv (envInsert var a)
|
modifyGlobalEnv (envInsert name addr)
|
||||||
pure v
|
pure v
|
||||||
|
where name = freeVariable (subterm assignmentTarget)
|
||||||
|
|
||||||
-- | Post increment operator (e.g. 1++ in Go, or i++ in C).
|
-- | Post increment operator (e.g. 1++ in Go, or i++ in C).
|
||||||
newtype PostIncrement a = PostIncrement a
|
newtype PostIncrement a = PostIncrement a
|
||||||
|
Loading…
Reference in New Issue
Block a user