mirror of
https://github.com/koalaman/shellcheck
synced 2025-07-06 04:51:37 -07:00
Warn about looping over array values and using them as keys
This commit is contained in:
parent
bb0a571a1e
commit
8c0bf8d41f
2 changed files with 85 additions and 0 deletions
|
@ -7,6 +7,7 @@
|
|||
- SC2293/SC2294: Warn when calling `eval` with arrays
|
||||
- SC2295: Warn about "${x#$y}" treating $y as a pattern when not quoted
|
||||
- SC2296-SC2301: Improved warnings for bad parameter expansions
|
||||
- SC2302/SC2303: Warn about loops over array values when using them as keys
|
||||
|
||||
### Fixed
|
||||
- SC2102 about repetitions in ranges no longer triggers on [[ -v arr[xx] ]]
|
||||
|
|
|
@ -65,6 +65,7 @@ treeChecks = [
|
|||
,checkArrayAssignmentIndices
|
||||
,checkUseBeforeDefinition
|
||||
,checkAliasUsedInSameParsingUnit
|
||||
,checkArrayValueUsedAsIndex
|
||||
]
|
||||
|
||||
runAnalytics :: AnalysisSpec -> [TokenComment]
|
||||
|
@ -4484,6 +4485,89 @@ checkUnquotedParameterExpansionPattern params x =
|
|||
surroundWith (getId t) params "\""
|
||||
|
||||
|
||||
prop_checkArrayValueUsedAsIndex1 = verifyTree checkArrayValueUsedAsIndex "for i in ${arr[@]}; do echo ${arr[i]}; done"
|
||||
prop_checkArrayValueUsedAsIndex2 = verifyTree checkArrayValueUsedAsIndex "for i in ${arr[@]}; do echo ${arr[$i]}; done"
|
||||
prop_checkArrayValueUsedAsIndex3 = verifyTree checkArrayValueUsedAsIndex "for i in ${arr[@]}; do echo $((arr[i])); done"
|
||||
prop_checkArrayValueUsedAsIndex4 = verifyTree checkArrayValueUsedAsIndex "for i in ${arr1[@]} ${arr2[@]}; do echo ${arr1[$i]}; done"
|
||||
prop_checkArrayValueUsedAsIndex5 = verifyTree checkArrayValueUsedAsIndex "for i in ${arr1[@]} ${arr2[@]}; do echo ${arr2[$i]}; done"
|
||||
prop_checkArrayValueUsedAsIndex7 = verifyNotTree checkArrayValueUsedAsIndex "for i in ${arr[@]}; do echo ${arr[K]}; done"
|
||||
prop_checkArrayValueUsedAsIndex8 = verifyNotTree checkArrayValueUsedAsIndex "for i in ${arr[@]}; do i=42; echo ${arr[i]}; done"
|
||||
prop_checkArrayValueUsedAsIndex9 = verifyNotTree checkArrayValueUsedAsIndex "for i in ${arr[@]}; do echo ${arr2[i]}; done"
|
||||
|
||||
checkArrayValueUsedAsIndex params _ =
|
||||
doVariableFlowAnalysis read write Map.empty (variableFlow params)
|
||||
where
|
||||
write loop@T_ForIn {} _ name (DataString (SourceFrom words)) = do
|
||||
modify $ Map.insert name (loop, mapMaybe f words)
|
||||
return []
|
||||
where
|
||||
f x = do
|
||||
name <- getArrayName x
|
||||
return (x, name)
|
||||
|
||||
write _ _ name _ = do
|
||||
modify $ Map.delete name
|
||||
return []
|
||||
|
||||
read _ t name = do
|
||||
varMap <- get
|
||||
return $ fromMaybe [] $ do
|
||||
(loop, arrays) <- Map.lookup name varMap
|
||||
(arrayRef, arrayName) <- getArrayIfUsedAsIndex name t
|
||||
-- Is this one of the 'for' arrays?
|
||||
(loopWord, _) <- find ((==arrayName) . snd) arrays
|
||||
-- Are we still in this loop?
|
||||
guard $ getId loop `elem` map getId (getPath parents t)
|
||||
return [
|
||||
makeComment WarningC (getId loopWord) 2302 "This loops over values. To loop over keys, use \"${!array[@]}\".",
|
||||
makeComment WarningC (getId arrayRef) 2303 $ (e4m name) ++ " is an array value, not a key. Use directly or loop over keys instead."
|
||||
]
|
||||
|
||||
parents = parentMap params
|
||||
|
||||
getArrayName :: Token -> Maybe String
|
||||
getArrayName t = do
|
||||
[T_DollarBraced _ _ l] <- return $ getWordParts t
|
||||
let str = concat $ oversimplify l
|
||||
guard $ getBracedModifier str == "[@]" && not ("!" `isPrefixOf` str)
|
||||
return $ getBracedReference str
|
||||
|
||||
-- This is much uglier than it should be
|
||||
getArrayIfUsedAsIndex :: String -> Token -> Maybe (Token, String)
|
||||
getArrayIfUsedAsIndex name t =
|
||||
case t of
|
||||
T_DollarBraced _ _ list -> do
|
||||
let ref = getBracedReference $ concat $ oversimplify list
|
||||
guard $ ref == name
|
||||
-- We found a $name. Look up the chain to see if it's ${arr[$name]}
|
||||
list@T_NormalWord {} <- Map.lookup (getId t) parents
|
||||
(T_DollarBraced _ _ parentList) <- Map.lookup (getId list) parents
|
||||
(T_Literal _ head : index : T_Literal _ tail : _) <- return $ getWordParts parentList
|
||||
let str = concat $ oversimplify list
|
||||
let modifier = getBracedModifier str
|
||||
guard $ getId index == getId t
|
||||
guard $ "[${VAR}]" `isPrefixOf` modifier
|
||||
return (t, getBracedReference str)
|
||||
|
||||
T_NormalWord wordId list -> do
|
||||
-- We found just name. Check if it's part of ${something[name]}
|
||||
parent@(T_DollarBraced _ _ parentList) <- Map.lookup wordId parents
|
||||
let str = concat $ oversimplify t
|
||||
let modifier = getBracedModifier str
|
||||
guard $ ("[" ++ name ++ "]") `isPrefixOf` modifier
|
||||
return (parent, getBracedReference str)
|
||||
|
||||
TA_Variable indexId ref [] -> do
|
||||
-- We found arithmetic name. See if it's part of arithmetic arr[name]
|
||||
guard $ ref == name
|
||||
(TA_Sequence seqId [element]) <- Map.lookup indexId parents
|
||||
guard $ getId element == indexId
|
||||
parent@(TA_Variable arrayId arrayName [element]) <- Map.lookup seqId parents
|
||||
guard $ getId element == seqId
|
||||
return (parent, arrayName)
|
||||
|
||||
_ -> Nothing
|
||||
|
||||
|
||||
return []
|
||||
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue