Skip to content

Commit

Permalink
SC2324: Warn when x+=1 appends.
Browse files Browse the repository at this point in the history
  • Loading branch information
koalaman committed Jul 30, 2023
1 parent c9e27c2 commit 372c0b6
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Git
### Added
- SC2324: Warn when x+=1 appends instead of increments.

### Fixed

Expand Down
9 changes: 9 additions & 0 deletions src/ShellCheck/ASTLib.hs
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,15 @@ isUnmodifiedParameterExpansion t =
in getBracedReference str == str
_ -> False

-- Return the referenced variable if (and only if) it's an unmodified parameter expansion.
getUnmodifiedParameterExpansion t =
case t of
T_DollarBraced _ _ list -> do
let str = concat $ oversimplify list
guard $ getBracedReference str == str
return str
_ -> Nothing

--- A list of the element and all its parents up to the root node.
getPath tree t = t :
case Map.lookup (getId t) tree of
Expand Down
38 changes: 38 additions & 0 deletions src/ShellCheck/Analytics.hs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ nodeChecks = [
,checkOverwrittenExitCode
,checkUnnecessaryArithmeticExpansionIndex
,checkUnnecessaryParens
,checkPlusEqualsNumber
]

optionalChecks = map fst optionalTreeChecks
Expand Down Expand Up @@ -5007,5 +5008,42 @@ checkUnnecessaryParens params t =
]


prop_checkPlusEqualsNumber1 = verify checkPlusEqualsNumber "x+=1"
prop_checkPlusEqualsNumber2 = verify checkPlusEqualsNumber "x+=42"
prop_checkPlusEqualsNumber3 = verifyNot checkPlusEqualsNumber "(( x += 1 ))"
prop_checkPlusEqualsNumber4 = verifyNot checkPlusEqualsNumber "declare -i x=0; x+=1"
prop_checkPlusEqualsNumber5 = verifyNot checkPlusEqualsNumber "x+='1'"
prop_checkPlusEqualsNumber6 = verifyNot checkPlusEqualsNumber "n=foo; x+=n"
prop_checkPlusEqualsNumber7 = verify checkPlusEqualsNumber "n=4; x+=n"
prop_checkPlusEqualsNumber8 = verify checkPlusEqualsNumber "n=4; x+=$n"
prop_checkPlusEqualsNumber9 = verifyNot checkPlusEqualsNumber "declare -ia var; var[x]+=1"
checkPlusEqualsNumber params t =
case t of
T_Assignment id Append var _ word -> sequence_ $ do
state <- CF.getIncomingState (cfgAnalysis params) id
guard $ isNumber state word
guard . not $ fromMaybe False $ CF.variableMayBeDeclaredInteger state var
return $ warn id 2324 "var+=1 will append, not increment. Use (( var += 1 )), declare -i var, or quote number to silence."
_ -> return ()

where
isNumber state word =
let
unquotedLiteral = getUnquotedLiteral word
isEmpty = unquotedLiteral == Just ""
isUnquotedNumber = not isEmpty && fromMaybe False (all isDigit <$> unquotedLiteral)
isNumericalVariableName = fromMaybe False $ do
str <- unquotedLiteral
CF.variableMayBeAssignedInteger state str
isNumericalVariableExpansion =
case word of
T_NormalWord _ [part] -> fromMaybe False $ do
str <- getUnmodifiedParameterExpansion part
CF.variableMayBeAssignedInteger state str
_ -> False
in
isUnquotedNumber || isNumericalVariableName || isNumericalVariableExpansion


return []
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
16 changes: 16 additions & 0 deletions src/ShellCheck/CFGAnalysis.hs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ module ShellCheck.CFGAnalysis (
,getIncomingState
,getOutgoingState
,doesPostDominate
,variableMayBeDeclaredInteger
,variableMayBeAssignedInteger
,ShellCheck.CFGAnalysis.runTests -- STRIP
) where

Expand Down Expand Up @@ -153,6 +155,20 @@ doesPostDominate analysis target base = fromMaybe False $ do
(targetStart, _) <- M.lookup target $ tokenToRange analysis
return $ targetStart `elem` (postDominators analysis ! baseEnd)

-- See if any execution path results in the variable containing a state
variableMayHaveState :: ProgramState -> String -> CFVariableProp -> Maybe Bool
variableMayHaveState state var property = do
value <- M.lookup var $ variablesInScope state
return $ any (S.member property) $ variableProperties value

-- See if any execution path declares the variable an integer (declare -i).
variableMayBeDeclaredInteger state var = variableMayHaveState state var CFVPInteger

-- See if any execution path suggests the variable may contain an integer value
variableMayBeAssignedInteger state var = do
value <- M.lookup var $ variablesInScope state
return $ (numericalStatus $ variableValue value) >= NumericalStatusMaybe

getDataForNode analysis node = M.lookup node $ nodeToData analysis

-- The current state of data flow at a point in the program, potentially as a diff
Expand Down

0 comments on commit 372c0b6

Please sign in to comment.