SC2318: Warn about backreferencing in `declare x=1 y=$x` (fixes #1653)

This commit is contained in:
Vidar Holen 2022-07-22 11:25:07 -07:00
parent 95b3cbf071
commit 9caeec104b
2 changed files with 54 additions and 0 deletions

View File

@ -2,6 +2,7 @@
### Added ### Added
- SC2316: Warn about 'local readonly foo' and similar (thanks, patrickxia!) - SC2316: Warn about 'local readonly foo' and similar (thanks, patrickxia!)
- SC2317: Warn about unreachable commands - SC2317: Warn about unreachable commands
- SC2318: Warn about backreferences in 'declare x=1 y=$x'
### Fixed ### Fixed
- SC2086: Now uses DFA to make more accurate predictions about values - SC2086: Now uses DFA to make more accurate predictions about values

View File

@ -27,6 +27,8 @@ module ShellCheck.Checks.Commands (checker, optionalChecks, ShellCheck.Checks.Co
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib import ShellCheck.AnalyzerLib
import ShellCheck.CFG
import qualified ShellCheck.CFGAnalysis as CF
import ShellCheck.Data import ShellCheck.Data
import ShellCheck.Interface import ShellCheck.Interface
import ShellCheck.Parser import ShellCheck.Parser
@ -37,12 +39,16 @@ import Control.Monad
import Control.Monad.RWS import Control.Monad.RWS
import Data.Char import Data.Char
import Data.Functor.Identity import Data.Functor.Identity
import qualified Data.Graph.Inductive.Graph as G
import Data.List import Data.List
import Data.Maybe import Data.Maybe
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Test.QuickCheck.All (forAllProperties) import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess) import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)
import Debug.Trace -- STRIP
data CommandName = Exactly String | Basename String data CommandName = Exactly String | Basename String
deriving (Eq, Ord) deriving (Eq, Ord)
@ -102,6 +108,7 @@ commandChecks = [
++ map checkArgComparison ("alias" : declaringCommands) ++ map checkArgComparison ("alias" : declaringCommands)
++ map checkMaskedReturns declaringCommands ++ map checkMaskedReturns declaringCommands
++ map checkMultipleDeclaring declaringCommands ++ map checkMultipleDeclaring declaringCommands
++ map checkBackreferencingDeclaration declaringCommands
optionalChecks = map fst optionalCommandChecks optionalChecks = map fst optionalCommandChecks
@ -1405,5 +1412,51 @@ checkEvalArray = CommandCheck (Exactly "eval") (mapM_ check . concatMap getWordP
_ -> False _ -> False
prop_checkBackreferencingDeclaration1 = verify (checkBackreferencingDeclaration "declare") "declare x=1 y=foo$x"
prop_checkBackreferencingDeclaration2 = verify (checkBackreferencingDeclaration "readonly") "readonly x=1 y=$((1+x))"
prop_checkBackreferencingDeclaration3 = verify (checkBackreferencingDeclaration "local") "local x=1 y=$(echo $x)"
prop_checkBackreferencingDeclaration4 = verify (checkBackreferencingDeclaration "local") "local x=1 y[$x]=z"
prop_checkBackreferencingDeclaration5 = verify (checkBackreferencingDeclaration "declare") "declare x=var $x=1"
prop_checkBackreferencingDeclaration6 = verify (checkBackreferencingDeclaration "declare") "declare x=var $x=1"
prop_checkBackreferencingDeclaration7 = verify (checkBackreferencingDeclaration "declare") "declare x=var $k=$x"
checkBackreferencingDeclaration cmd = CommandCheck (Exactly cmd) check
where
check t = foldM_ perArg M.empty $ arguments t
perArg leftArgs t =
case t of
T_Assignment id _ name idx t -> do
warnIfBackreferencing leftArgs $ t:idx
return $ M.insert name id leftArgs
t -> do
warnIfBackreferencing leftArgs [t]
return leftArgs
warnIfBackreferencing backrefs l = do
references <- findReferences l
let reused = M.intersection backrefs references
mapM msg $ M.toList reused
msg (name, id) = warn id 2318 $ "This assignment is used again in this '" ++ cmd ++ "', but won't have taken effect. Use two '" ++ cmd ++ "'s."
findReferences list = do
cfga <- asks cfgAnalysis
let graph = CF.graph cfga
let nodesMap = CF.tokenToNodes cfga
let nodes = S.unions $ map (\id -> M.findWithDefault S.empty id nodesMap) $ map getId $ list
let labels = mapMaybe (G.lab graph) $ S.toList nodes
let references = M.fromList $ concatMap refFromLabel labels
return references
refFromLabel lab =
case lab of
CFApplyEffects effects -> mapMaybe refFromEffect effects
_ -> []
refFromEffect e =
case e of
IdTagged id (CFReadVariable name) -> return (name, id)
_ -> Nothing
return [] return []
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |]) runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])