Control Flow Graph / Data Flow Analysis support

This commit is contained in:
Vidar Holen 2022-07-19 14:23:27 -07:00
parent 7946bf5657
commit f77a545282
17 changed files with 2909 additions and 135 deletions

View file

@ -0,0 +1,101 @@
{-
Copyright 2022 Vidar Holen
This file is part of ShellCheck.
https://www.shellcheck.net
ShellCheck is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ShellCheck is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE TemplateHaskell #-}
-- Checks that run on the Control Flow Graph (as opposed to the AST)
-- This is scaffolding for a work in progress.
module ShellCheck.Checks.ControlFlow (checker, optionalChecks, ShellCheck.Checks.ControlFlow.runTests) where
import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.CFG hiding (cfgAnalysis)
import ShellCheck.CFGAnalysis
import ShellCheck.AnalyzerLib
import ShellCheck.Data
import ShellCheck.Interface
import Control.Monad
import Control.Monad.Reader
import Data.Graph.Inductive.Graph
import qualified Data.Map as M
import qualified Data.Set as S
import Data.List
import Data.Maybe
import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)
optionalChecks :: [CheckDescription]
optionalChecks = []
-- A check that runs on the entire graph
type ControlFlowCheck = Analysis
-- A check invoked once per node, with its (pre,post) data
type ControlFlowNodeCheck = LNode CFNode -> (ProgramState, ProgramState) -> Analysis
-- A check invoked once per effect, with its node's (pre,post) data
type ControlFlowEffectCheck = IdTagged CFEffect -> Node -> (ProgramState, ProgramState) -> Analysis
checker :: AnalysisSpec -> Parameters -> Checker
checker spec params = Checker {
perScript = const $ sequence_ controlFlowChecks,
perToken = const $ return ()
}
controlFlowChecks :: [ControlFlowCheck]
controlFlowChecks = [
runNodeChecks controlFlowNodeChecks
]
controlFlowNodeChecks :: [ControlFlowNodeCheck]
controlFlowNodeChecks = [
runEffectChecks controlFlowEffectChecks
]
controlFlowEffectChecks :: [ControlFlowEffectCheck]
controlFlowEffectChecks = [
]
runNodeChecks :: [ControlFlowNodeCheck] -> ControlFlowCheck
runNodeChecks perNode = do
cfg <- asks cfgAnalysis
runOnAll cfg
where
getData datas n@(node, label) = do
(pre, post) <- M.lookup node datas
return (n, (pre, post))
runOn :: (LNode CFNode, (ProgramState, ProgramState)) -> Analysis
runOn (node, prepost) = mapM_ (\c -> c node prepost) perNode
runOnAll cfg = mapM_ runOn $ mapMaybe (getData $ nodeToData cfg) $ labNodes (graph cfg)
runEffectChecks :: [ControlFlowEffectCheck] -> ControlFlowNodeCheck
runEffectChecks list = checkNode
where
checkNode (node, label) prepost =
case label of
CFApplyEffects effects -> mapM_ (\effect -> mapM_ (\c -> c effect node prepost) list) effects
_ -> return ()
return []
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])