Example plumbing for Portage variables

This commit is contained in:
Vidar Holen 2023-08-13 17:49:36 -07:00
parent 90d3172dfe
commit 0138a6fafc
9 changed files with 96 additions and 59 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -117,7 +117,8 @@ dummySystemInterface = mockedSystemInterface [
cfgParams :: CFGParameters
cfgParams = CFGParameters {
cfLastpipe = False,
cfPipefail = False
cfPipefail = False,
cfAdditionalInitialVariables = []
}
-- An example script to play with

View file

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