Skip to content

Commit

Permalink
Example plumbing for Portage variables
Browse files Browse the repository at this point in the history
  • Loading branch information
koalaman committed Aug 14, 2023
1 parent 90d3172 commit 0138a6f
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 59 deletions.
16 changes: 15 additions & 1 deletion shellcheck.hs
Original file line number Diff line number Diff line change
Expand Up @@ -396,10 +396,12 @@ ioInterface options files = do
inputs <- mapM normalize files
cache <- newIORef emptyCache
configCache <- newIORef ("", Nothing)
portageVars <- newIORef Nothing
return (newSystemInterface :: SystemInterface IO) {
siReadFile = get cache inputs,
siFindSource = findSourceFile inputs (sourcePaths options),
siGetConfig = getConfig configCache
siGetConfig = getConfig configCache,
siGetPortageVariables = getOrLoadPortage portageVars
}
where
emptyCache :: Map.Map FilePath String
Expand Down Expand Up @@ -523,6 +525,18 @@ ioInterface options files = do
("SCRIPTDIR":rest) -> joinPath (scriptdir:rest)
_ -> str

getOrLoadPortage cache = do
x <- readIORef cache
case x of
Just m -> do
hPutStrLn stderr "Reusing previous Portage variables"
return m
Nothing -> do
hPutStrLn stderr "Computing Portage variables"
vars <- return $ Map.fromList [("foo", ["bar", "baz"])] -- TODO: Actually read the variables
writeIORef cache $ Just vars
return vars

inputFile file = do
(handle, shouldCache) <-
if file == "-"
Expand Down
4 changes: 2 additions & 2 deletions src/ShellCheck/Analytics.hs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ runAndGetComments f s = do
let pr = pScript s
root <- prRoot pr
let spec = defaultSpec pr
let params = makeParameters spec
let params = runIdentity $ makeParameters (mockedSystemInterface []) spec
return $
filterByAnnotation spec params $
f params root
Expand Down Expand Up @@ -2451,7 +2451,7 @@ checkUnassignedReferences = checkUnassignedReferences' False
checkUnassignedReferences' includeGlobals params t = warnings
where
(readMap, writeMap) = execState (mapM tally $ variableFlow params) (Map.empty, Map.empty)
defaultAssigned = Map.fromList $ map (\a -> (a, ())) $ filter (not . null) internalVariables
defaultAssigned = Map.fromList $ map (\a -> (a, ())) $ filter (not . null) (internalVariables ++ additionalKnownVariables params)

tally (Assignment (_, _, name, _)) =
modify (\(read, written) -> (read, Map.insert name () written))
Expand Down
16 changes: 8 additions & 8 deletions src/ShellCheck/Analyzer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ import qualified ShellCheck.Checks.ShellSupport


-- TODO: Clean up the cruft this is layered on
analyzeScript :: AnalysisSpec -> AnalysisResult
analyzeScript spec = newAnalysisResult {
arComments =
filterByAnnotation spec params . nub $
runChecker params (checkers spec params)
}
where
params = makeParameters spec
analyzeScript :: Monad m => SystemInterface m -> AnalysisSpec -> m AnalysisResult
analyzeScript sys spec = do
params <- makeParameters sys spec
return $ newAnalysisResult {
arComments =
filterByAnnotation spec params . nub $
runChecker params (checkers spec params)
}

checkers spec params = mconcat $ map ($ params) [
ShellCheck.Analytics.checker spec,
Expand Down
87 changes: 51 additions & 36 deletions src/ShellCheck/AnalyzerLib.hs
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ data Parameters = Parameters {
-- map from token id to start and end position
tokenPositions :: Map.Map Id (Position, Position),
-- Result from Control Flow Graph analysis (including data flow analysis)
cfgAnalysis :: CF.CFGAnalysis
cfgAnalysis :: CF.CFGAnalysis,
-- A set of additional variables known to be set (TODO: make this more general?)
additionalKnownVariables :: [String]
} deriving (Show)

-- TODO: Cache results of common AST ops here
Expand Down Expand Up @@ -152,7 +154,7 @@ producesComments c s = do
let pr = pScript s
prRoot pr
let spec = defaultSpec pr
let params = makeParameters spec
let params = runIdentity $ makeParameters (mockedSystemInterface []) spec
return . not . null $ filterByAnnotation spec params $ runChecker params c

makeComment :: Severity -> Id -> Code -> String -> TokenComment
Expand Down Expand Up @@ -196,41 +198,54 @@ makeCommentWithFix severity id code str fix =
}
in force withFix

makeParameters spec = params
makeParameters :: Monad m => SystemInterface m -> AnalysisSpec -> m Parameters
makeParameters sys spec = do
extraVars <-
case shell of
Bash -> do -- TODO: EBuild type
vars <- siGetPortageVariables sys
return $ Map.findWithDefault [] "foo" vars -- TODO: Determine what to look up in map
_ -> return []
return $ makeParams extraVars
where
params = Parameters {
rootNode = root,
shellType = fromMaybe (determineShell (asFallbackShell spec) root) $ asShellType spec,
hasSetE = containsSetE root,
hasLastpipe =
case shellType params of
Bash -> isOptionSet "lastpipe" root
Dash -> False
Sh -> False
Ksh -> True,
hasInheritErrexit =
case shellType params of
Bash -> isOptionSet "inherit_errexit" root
Dash -> True
Sh -> True
Ksh -> False,
hasPipefail =
case shellType params of
Bash -> isOptionSet "pipefail" root
Dash -> True
Sh -> True
Ksh -> isOptionSet "pipefail" root,
shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShell spec),
idMap = getTokenMap root,
parentMap = getParentTree root,
variableFlow = getVariableFlow params root,
tokenPositions = asTokenPositions spec,
cfgAnalysis = CF.analyzeControlFlow cfParams root
}
cfParams = CF.CFGParameters {
CF.cfLastpipe = hasLastpipe params,
CF.cfPipefail = hasPipefail params
}
shell = fromMaybe (determineShell (asFallbackShell spec) root) $ asShellType spec
makeParams extraVars = params
where
params = Parameters {
rootNode = root,
shellType = shell,
hasSetE = containsSetE root,
hasLastpipe =
case shellType params of
Bash -> isOptionSet "lastpipe" root
Dash -> False
Sh -> False
Ksh -> True,
hasInheritErrexit =
case shellType params of
Bash -> isOptionSet "inherit_errexit" root
Dash -> True
Sh -> True
Ksh -> False,
hasPipefail =
case shellType params of
Bash -> isOptionSet "pipefail" root
Dash -> True
Sh -> True
Ksh -> isOptionSet "pipefail" root,
shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShell spec),
idMap = getTokenMap root,
parentMap = getParentTree root,
variableFlow = getVariableFlow params root,
tokenPositions = asTokenPositions spec,
cfgAnalysis = CF.analyzeControlFlow cfParams root,
additionalKnownVariables = extraVars
}
cfParams = CF.CFGParameters {
CF.cfLastpipe = hasLastpipe params,
CF.cfPipefail = hasPipefail params,
CF.cfAdditionalInitialVariables = additionalKnownVariables params
}
root = asScript spec


Expand Down
4 changes: 3 additions & 1 deletion src/ShellCheck/CFG.hs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ data CFGParameters = CFGParameters {
-- Whether the last element in a pipeline runs in the current shell
cfLastpipe :: Bool,
-- Whether all elements in a pipeline count towards the exit status
cfPipefail :: Bool
cfPipefail :: Bool,
-- Additional variables to consider defined
cfAdditionalInitialVariables :: [String]
}

data CFGResult = CFGResult {
Expand Down
9 changes: 5 additions & 4 deletions src/ShellCheck/CFGAnalysis.hs
Original file line number Diff line number Diff line change
Expand Up @@ -197,12 +197,13 @@ unreachableState = modified newInternalState {
}

-- The default state we assume we get from the environment
createEnvironmentState :: InternalState
createEnvironmentState = do
createEnvironmentState :: CFGParameters -> InternalState
createEnvironmentState params = do
foldl' (flip ($)) newInternalState $ concat [
addVars Data.internalVariables unknownVariableState,
addVars Data.variablesWithoutSpaces spacelessVariableState,
addVars Data.specialIntegerVariables integerVariableState
addVars Data.specialIntegerVariables integerVariableState,
addVars (cfAdditionalInitialVariables params) unknownVariableState
]
where
addVars names val = map (\name -> insertGlobal name val) names
Expand Down Expand Up @@ -1344,7 +1345,7 @@ analyzeControlFlow params t =
runST $ f cfg entry exit
where
f cfg entry exit = do
let env = createEnvironmentState
let env = createEnvironmentState params
ctx <- newCtx $ cfGraph cfg
-- Do a dataflow analysis starting on the root node
exitState <- runRoot ctx env entry exit
Expand Down
9 changes: 5 additions & 4 deletions src/ShellCheck/Checker.hs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ checkScript sys spec = do
asTokenPositions = tokenPositions,
asOptionalChecks = getEnableDirectives root ++ csOptionalChecks spec
} where as = newAnalysisSpec root
let analysisMessages =
maybe []
(arComments . analyzeScript . analysisSpec)
$ prRoot result
let getAnalysisMessages =
case prRoot result of
Just root -> arComments <$> (analyzeScript sys $ analysisSpec root)
Nothing -> return []
let translator = tokenToPosition tokenPositions
analysisMessages <- getAnalysisMessages
return . nub . sortMessages . filter shouldInclude $
(parseMessages ++ map translator analysisMessages)

Expand Down
3 changes: 2 additions & 1 deletion src/ShellCheck/Debug.hs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ dummySystemInterface = mockedSystemInterface [
cfgParams :: CFGParameters
cfgParams = CFGParameters {
cfLastpipe = False,
cfPipefail = False
cfPipefail = False,
cfAdditionalInitialVariables = []
}

-- An example script to play with
Expand Down
7 changes: 5 additions & 2 deletions src/ShellCheck/Interface.hs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ data SystemInterface m = SystemInterface {
-- find the sourced file
siFindSource :: String -> Maybe Bool -> [String] -> String -> m FilePath,
-- | Get the configuration file (name, contents) for a filename
siGetConfig :: String -> m (Maybe (FilePath, String))
siGetConfig :: String -> m (Maybe (FilePath, String)),
-- | Look up Portage Eclass variables
siGetPortageVariables :: m (Map.Map String [String])
}

-- ShellCheck input and output
Expand Down Expand Up @@ -141,7 +143,8 @@ newSystemInterface =
SystemInterface {
siReadFile = \_ _ -> return $ Left "Not implemented",
siFindSource = \_ _ _ name -> return name,
siGetConfig = \_ -> return Nothing
siGetConfig = \_ -> return Nothing,
siGetPortageVariables = return Map.empty
}

-- Parser input and output
Expand Down

0 comments on commit 0138a6f

Please sign in to comment.