{- 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 . -} {-# 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 }) ) |])