1
1
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:
Rob Rix 2018-03-15 16:07:58 -04:00 committed by GitHub
commit 8701de3af0
8 changed files with 137 additions and 40 deletions

View File

@ -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
View 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 instances 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 its 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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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