diff --git a/ShellCheck/AST.hs b/ShellCheck/AST.hs index 0bae4e2..437ad7a 100644 --- a/ShellCheck/AST.hs +++ b/ShellCheck/AST.hs @@ -23,7 +23,7 @@ import qualified Text.Regex as Re data Id = Id Int deriving (Show, Eq, Ord) -data Token = T_AND_IF Id | T_OR_IF Id | T_DSEMI Id | T_Semi Id | T_DLESS Id | T_DGREAT Id | T_LESSAND Id | T_GREATAND Id | T_LESSGREAT Id | T_DLESSDASH Id | T_CLOBBER Id | T_If Id | T_Then Id | T_Else Id | T_Elif Id | T_Fi Id | T_Do Id | T_Done Id | T_Case Id | T_Esac Id | T_While Id | T_Until Id | T_For Id | T_Lbrace Id | T_Rbrace Id | T_Lparen Id | T_Rparen Id | T_Bang Id | T_In Id | T_NEWLINE Id | T_EOF Id | T_Less Id | T_Greater Id | T_SingleQuoted Id String | T_Literal Id String | T_NormalWord Id [Token] | T_DoubleQuoted Id [Token] | T_DollarExpansion Id [Token] | T_DollarBraced Id Token | T_DollarArithmetic Id Token | T_BraceExpansion Id String | T_IoFile Id Token Token | T_HereDoc Id Bool Bool String | T_HereString Id Token | T_FdRedirect Id String Token | T_Assignment Id String Token | T_Array Id [Token] | T_Redirecting Id [Token] Token | T_SimpleCommand Id [Token] [Token] | T_Pipeline Id [Token] | T_Banged Id Token | T_AndIf Id (Token) (Token) | T_OrIf Id (Token) (Token) | T_Backgrounded Id Token | T_IfExpression Id [([Token],[Token])] [Token] | T_Subshell Id [Token] | T_BraceGroup Id [Token] | T_WhileExpression Id [Token] [Token] | T_UntilExpression Id [Token] [Token] | T_ForIn Id String [Token] [Token] | T_CaseExpression Id Token [([Token],[Token])] | T_Function Id String Token | T_Arithmetic Id Token | T_Script Id String [Token] | T_Condition Id ConditionType Token | T_Extglob Id String [Token] | TC_And Id ConditionType String Token Token | TC_Or Id ConditionType String Token Token | TC_Group Id ConditionType Token | TC_Binary Id ConditionType String Token Token | TC_Unary Id ConditionType String Token | TC_Noary Id ConditionType Token | TA_Binary Id String Token Token | TA_Unary Id String Token | TA_Sequence Id [Token] | TA_Variable Id String | TA_Trinary Id Token Token Token | TA_Expansion Id Token | TA_Literal Id String | T_Backticked Id String | T_ProcSub Id String [Token] | T_Glob Id String | T_ForArithmetic Id Token Token Token [Token] | T_DollarSingleQuoted Id String | T_DollarDoubleQuoted Id [Token] +data Token = T_AND_IF Id | T_OR_IF Id | T_DSEMI Id | T_Semi Id | T_DLESS Id | T_DGREAT Id | T_LESSAND Id | T_GREATAND Id | T_LESSGREAT Id | T_DLESSDASH Id | T_CLOBBER Id | T_If Id | T_Then Id | T_Else Id | T_Elif Id | T_Fi Id | T_Do Id | T_Done Id | T_Case Id | T_Esac Id | T_While Id | T_Until Id | T_For Id | T_Select Id | T_Lbrace Id | T_Rbrace Id | T_Lparen Id | T_Rparen Id | T_Bang Id | T_In Id | T_NEWLINE Id | T_EOF Id | T_Less Id | T_Greater Id | T_SingleQuoted Id String | T_Literal Id String | T_NormalWord Id [Token] | T_DoubleQuoted Id [Token] | T_DollarExpansion Id [Token] | T_DollarBraced Id Token | T_DollarArithmetic Id Token | T_BraceExpansion Id String | T_IoFile Id Token Token | T_HereDoc Id Bool Bool String | T_HereString Id Token | T_FdRedirect Id String Token | T_Assignment Id String Token | T_Array Id [Token] | T_Redirecting Id [Token] Token | T_SimpleCommand Id [Token] [Token] | T_Pipeline Id [Token] | T_Banged Id Token | T_AndIf Id (Token) (Token) | T_OrIf Id (Token) (Token) | T_Backgrounded Id Token | T_IfExpression Id [([Token],[Token])] [Token] | T_Subshell Id [Token] | T_BraceGroup Id [Token] | T_WhileExpression Id [Token] [Token] | T_UntilExpression Id [Token] [Token] | T_ForIn Id String [Token] [Token] | T_SelectIn Id String [Token] [Token] | T_CaseExpression Id Token [([Token],[Token])] | T_Function Id String Token | T_Arithmetic Id Token | T_Script Id String [Token] | T_Condition Id ConditionType Token | T_Extglob Id String [Token] | TC_And Id ConditionType String Token Token | TC_Or Id ConditionType String Token Token | TC_Group Id ConditionType Token | TC_Binary Id ConditionType String Token Token | TC_Unary Id ConditionType String Token | TC_Noary Id ConditionType Token | TA_Binary Id String Token Token | TA_Unary Id String Token | TA_Sequence Id [Token] | TA_Variable Id String | TA_Trinary Id Token Token Token | TA_Expansion Id Token | TA_Literal Id String | T_Backticked Id String | T_ProcSub Id String [Token] | T_Glob Id String | T_ForArithmetic Id Token Token Token [Token] | T_DollarSingleQuoted Id String | T_DollarDoubleQuoted Id [Token] | TA_Base Id String Token deriving (Show) @@ -96,6 +96,7 @@ analyze f g i t = delve (T_WhileExpression id c l) = dll c l $ T_WhileExpression id delve (T_UntilExpression id c l) = dll c l $ T_UntilExpression id delve (T_ForIn id v w l) = dll w l $ T_ForIn id v + delve (T_SelectIn id v w l) = dll w l $ T_SelectIn id v delve (T_CaseExpression id word cases) = do newWord <- round word newCases <- mapM (\(c, t) -> do @@ -134,6 +135,7 @@ analyze f g i t = c <- round t3 return $ TA_Trinary id a b c delve (TA_Expansion id t) = d1 t $ TA_Expansion id + delve (TA_Base id b t) = d1 t $ TA_Base id b delve t = return t getId t = case t of @@ -160,6 +162,7 @@ getId t = case t of T_While id -> id T_Until id -> id T_For id -> id + T_Select id -> id T_Lbrace id -> id T_Rbrace id -> id T_Lparen id -> id @@ -197,6 +200,7 @@ getId t = case t of T_WhileExpression id _ _ -> id T_UntilExpression id _ _ -> id T_ForIn id _ _ _ -> id + T_SelectIn id _ _ _ -> id T_CaseExpression id _ _ -> id T_Function id _ _ -> id T_Arithmetic id _ -> id @@ -217,6 +221,7 @@ getId t = case t of TA_Trinary id _ _ _ -> id TA_Expansion id _ -> id TA_Literal id _ -> id + TA_Base id _ _ -> id T_ProcSub id _ _ -> id T_Glob id _ -> id T_ForArithmetic id _ _ _ _ -> id diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs index d9d9b16..b71fac8 100644 --- a/ShellCheck/Analytics.hs +++ b/ShellCheck/Analytics.hs @@ -38,7 +38,6 @@ checks = concat [ runAllAnalytics = checkList checks checkList l t m = foldl (\x f -> f t x) m l -checkList l t m = foldl (\x f -> f t x) m l runBasicAnalysis f t m = snd $ runState (doAnalysis f t) m basicChecks = [ @@ -58,6 +57,7 @@ basicChecks = [ ,checkForDecimals ,checkDivBeforeMult ,checkArithmeticDeref + ,checkArithmeticBadOctal ,checkComparisonAgainstGlob ,checkPrintfVar ,checkCommarrays @@ -211,7 +211,7 @@ checkPipePitfalls (T_Pipeline id commands) = do for [["grep"], ["sed"]] $ \id -> style id "You don't need grep | sed, sed can filter lines by itself." for [["grep"], ["awk"]] $ \id -> style id "You don't need grep | awk, awk can filter lines by itself." for [["ls"], ["?"]] $ \id -> warn id "Don't parse ls output; it mangles filenames." - for [["ls"], ["grep"]] $ \id -> warn id "Don't use ls | grep. Use a for loop with a condition in it." + for [["ls"], ["grep"]] $ \id -> warn id "Don't use ls | grep. Use a glob or a for loop with a condition." for [["ls"], ["xargs"]] $ \id -> warn id "Don't use ls | xargs. Use find -exec .. +" for [["find"], ["xargs"]]$ \id -> warn id "Don't use find | xargs cmd. find -exec cmd {} + handles whitespace." for [["?"], ["echo"]] $ \id -> info id "echo doesn't read from stdin, are you sure you should be piping to it?" @@ -263,6 +263,7 @@ checkUndeclaredBash t@(T_Script id sb _) m = bashism (T_DollarDoubleQuoted id _) = warnMsg id "$\"..\"" bashism (T_ForArithmetic id _ _ _ _) = warnMsg id "arithmetic for loops" bashism (T_Arithmetic id _) = warnMsg id "((..))" + bashism (T_SelectIn id _ _ _) = warnMsg id "select loops" bashism _ = return() prop_checkForInQuoted = verify checkForInQuoted "for f in \"$(ls)\"; do echo foo; done" @@ -509,6 +510,12 @@ checkArithmeticDeref (TA_Expansion _ (T_DollarBraced id l)) | not . excepting $ excepting s = (any (`elem` "/.:#%?*@") s) || (isDigit $ head s) checkArithmeticDeref _ = return () +prop_checkArithmeticBadOctal1 = verify checkArithmeticBadOctal "(( 0192 ))" +prop_checkArithmeticBadOctal2 = verifyNot checkArithmeticBadOctal "(( 0x192 ))" +prop_checkArithmeticBadOctal3 = verifyNot checkArithmeticBadOctal "(( 1 ^ 0777 ))" +checkArithmeticBadOctal (TA_Base id "0" (TA_Literal _ str)) | '9' `elem` str || '8' `elem` str = + err id $ "Numbers with leading 0 are considered octal." +checkArithmeticBadOctal _ = return () prop_checkComparisonAgainstGlob = verify checkComparisonAgainstGlob "[[ $cow == $bar ]]" prop_checkComparisonAgainstGlob2 = verifyNot checkComparisonAgainstGlob "[[ $cow == \"$bar\" ]]" @@ -782,7 +789,8 @@ getModifiedVariablesWithType spacefulF t = else [] --Points to 'for' rather than variable - T_ForIn id str words _ -> [(id, str, if any (isSpaceful spacefulF) words then Spaceful else Spaceless)] + T_ForIn id str words _ -> [(id, str, if any (isSpaceful spacefulF) words || null words then Spaceful else Spaceless)] + T_SelectIn id str words _ -> [(id, str, if any (isSpaceful spacefulF) words || null words then Spaceful else Spaceless)] _ -> [] isSpaceful :: (String -> Bool) -> Token -> Bool diff --git a/ShellCheck/Parser.hs b/ShellCheck/Parser.hs index a115eff..c2d57d3 100644 --- a/ShellCheck/Parser.hs +++ b/ShellCheck/Parser.hs @@ -343,6 +343,7 @@ prop_a7 = isOk readArithmeticContents "3*2**10" prop_a8 = isOk readArithmeticContents "3" prop_a9 = isOk readArithmeticContents "a^!-b" prop_aA = isOk readArithmeticContents "! $?" +prop_aB = isOk readArithmeticContents "10#08 * 16#f" readArithmeticContents = readSequence where @@ -381,9 +382,35 @@ readArithmeticContents = readNumber = do id <- getNextId num <- many1 $ oneOf "0123456789." - return $ TA_Literal id num + return $ TA_Literal id (num) - readArithTerm = readGroup <|> readExpansion <|> readNumber <|> readVar + readBased = getArbitrary <|> getHex <|> getOct + where + getThing prefix litchars = try $ do + id <- getNextId + x <- prefix + t <- readExpansion <|> (do + i <- getNextId + stuff <- many1 litchars + return $ TA_Literal i stuff) + return $ TA_Base id x t + + getArbitrary = getThing arbitrary variableChars + getHex = getThing hex hexDigit + getOct = getThing oct digit + + arbitrary = try $ do + b <- many1 digit + s <- char '#' + return (b ++ [s]) + hex = try $ do + z <- char '0' + x <- oneOf "xX" + return (z:x:[]) + oct = string "0" + + readArithTerm = readBased <|> readArithTermUnit + readArithTermUnit = readGroup <|> readExpansion <|> readNumber <|> readVar readSequence = do spacing @@ -1138,6 +1165,22 @@ readForClause = called "for loop" $ do values <- readInClause <|> (readSequentialSep >> return []) return $ \id group -> (return $ T_ForIn id name values group) +prop_readSelectClause1 = isOk readSelectClause "select foo in *; do echo $foo; done" +prop_readSelectClause2 = isOk readSelectClause "select foo; do echo $foo; done" +readSelectClause = called "select loop" $ do + pos <- getPosition + (T_Select id) <- g_Select + spacing + typ <- readRegular + group <- readDoGroup pos + typ id group + where + readRegular = do + name <- readVariableName + spacing + values <- readInClause <|> (readSequentialSep >> return []) + return $ \id group -> (return $ T_SelectIn id name values group) + readInClause = do g_In things <- (readCmdWord) `reluctantlyTill` @@ -1232,7 +1275,7 @@ readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` readCompoundCommand = do id <- getNextId - cmd <- choice [ readBraceGroup, readArithmeticExpression, readSubshell, readCondition, readWhileClause, readUntilClause, readIfClause, readForClause, readCaseClause, readFunctionDefinition] + cmd <- choice [ readBraceGroup, readArithmeticExpression, readSubshell, readCondition, readWhileClause, readUntilClause, readIfClause, readForClause, readSelectClause, readCaseClause, readFunctionDefinition] spacing redirs <- many readIoRedirect when (not . null $ redirs) $ optional $ do @@ -1334,6 +1377,7 @@ g_Esac = tryWordToken "esac" T_Esac g_While = tryWordToken "while" T_While g_Until = tryWordToken "until" T_Until g_For = tryWordToken "for" T_For +g_Select = tryWordToken "select" T_Select g_In = tryWordToken "in" T_In g_Lbrace = tryWordToken "{" T_Lbrace g_Rbrace = tryWordToken "}" T_Rbrace