{-
    Copyright 2012-2019 Vidar Holen

    This file is part of ShellCheck.
    https://www.shellcheck.net

    ShellCheck is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ShellCheck is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}

-- This module contains checks that examine specific commands by name.
module ShellCheck.Checks.Commands (checker , ShellCheck.Checks.Commands.runTests) where

import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib
import ShellCheck.Data
import ShellCheck.Interface
import ShellCheck.Parser
import ShellCheck.Regex

import Control.Monad
import Control.Monad.RWS
import Data.Char
import Data.List
import Data.Maybe
import qualified Data.Map.Strict as Map
import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)

data CommandName = Exactly String | Basename String
    deriving (CommandName -> CommandName -> Bool
(CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool) -> Eq CommandName
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: CommandName -> CommandName -> Bool
$c/= :: CommandName -> CommandName -> Bool
== :: CommandName -> CommandName -> Bool
$c== :: CommandName -> CommandName -> Bool
Eq, Eq CommandName
Eq CommandName =>
(CommandName -> CommandName -> Ordering)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> Bool)
-> (CommandName -> CommandName -> CommandName)
-> (CommandName -> CommandName -> CommandName)
-> Ord CommandName
CommandName -> CommandName -> Bool
CommandName -> CommandName -> Ordering
CommandName -> CommandName -> CommandName
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: CommandName -> CommandName -> CommandName
$cmin :: CommandName -> CommandName -> CommandName
max :: CommandName -> CommandName -> CommandName
$cmax :: CommandName -> CommandName -> CommandName
>= :: CommandName -> CommandName -> Bool
$c>= :: CommandName -> CommandName -> Bool
> :: CommandName -> CommandName -> Bool
$c> :: CommandName -> CommandName -> Bool
<= :: CommandName -> CommandName -> Bool
$c<= :: CommandName -> CommandName -> Bool
< :: CommandName -> CommandName -> Bool
$c< :: CommandName -> CommandName -> Bool
compare :: CommandName -> CommandName -> Ordering
$ccompare :: CommandName -> CommandName -> Ordering
$cp1Ord :: Eq CommandName
Ord)

data CommandCheck =
    CommandCheck CommandName (Token -> Analysis)


verify :: CommandCheck -> String -> Bool
verify :: CommandCheck -> String -> Bool
verify f :: CommandCheck
f s :: String
s = Checker -> String -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
verifyNot :: CommandCheck -> String -> Bool
verifyNot f :: CommandCheck
f s :: String
s = Checker -> String -> Maybe Bool
producesComments ([CommandCheck] -> Checker
getChecker [CommandCheck
f]) String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False

arguments :: Token -> [Token]
arguments (T_SimpleCommand _ _ (cmd :: Token
cmd:args :: [Token]
args)) = [Token]
args

commandChecks :: [CommandCheck]
commandChecks :: [CommandCheck]
commandChecks = [
    CommandCheck
checkTr
    ,CommandCheck
checkFindNameGlob
    ,CommandCheck
checkNeedlessExpr
    ,CommandCheck
checkGrepRe
    ,CommandCheck
checkTrapQuotes
    ,CommandCheck
checkReturn
    ,CommandCheck
checkExit
    ,CommandCheck
checkFindExecWithSingleArgument
    ,CommandCheck
checkUnusedEchoEscapes
    ,CommandCheck
checkInjectableFindSh
    ,CommandCheck
checkFindActionPrecedence
    ,CommandCheck
checkMkdirDashPM
    ,CommandCheck
checkNonportableSignals
    ,CommandCheck
checkInteractiveSu
    ,CommandCheck
checkSshCommandString
    ,CommandCheck
checkPrintfVar
    ,CommandCheck
checkUuoeCmd
    ,CommandCheck
checkSetAssignment
    ,CommandCheck
checkExportedExpansions
    ,CommandCheck
checkAliasesUsesArgs
    ,CommandCheck
checkAliasesExpandEarly
    ,CommandCheck
checkUnsetGlobs
    ,CommandCheck
checkFindWithoutPath
    ,CommandCheck
checkTimeParameters
    ,CommandCheck
checkTimedCommand
    ,CommandCheck
checkLocalScope
    ,CommandCheck
checkDeprecatedTempfile
    ,CommandCheck
checkDeprecatedEgrep
    ,CommandCheck
checkDeprecatedFgrep
    ,CommandCheck
checkWhileGetoptsCase
    ,CommandCheck
checkCatastrophicRm
    ,CommandCheck
checkLetUsage
    ,CommandCheck
checkMvArguments, CommandCheck
checkCpArguments, CommandCheck
checkLnArguments
    ,CommandCheck
checkFindRedirections
    ,CommandCheck
checkReadExpansions
    ,CommandCheck
checkWhich
    ,CommandCheck
checkSudoRedirect
    ,CommandCheck
checkSudoArgs
    ,CommandCheck
checkSourceArgs
    ,CommandCheck
checkChmodDashr
    ]

buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis)
buildCommandMap :: [CommandCheck] -> Map CommandName (Token -> Analysis)
buildCommandMap = (Map CommandName (Token -> Analysis)
 -> CommandCheck -> Map CommandName (Token -> Analysis))
-> Map CommandName (Token -> Analysis)
-> [CommandCheck]
-> Map CommandName (Token -> Analysis)
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis)
addCheck Map CommandName (Token -> Analysis)
forall k a. Map k a
Map.empty
  where
    addCheck :: Map CommandName (Token -> Analysis)
-> CommandCheck -> Map CommandName (Token -> Analysis)
addCheck map :: Map CommandName (Token -> Analysis)
map (CommandCheck name :: CommandName
name function :: Token -> Analysis
function) =
        ((Token -> Analysis) -> (Token -> Analysis) -> Token -> Analysis)
-> CommandName
-> (Token -> Analysis)
-> Map CommandName (Token -> Analysis)
-> Map CommandName (Token -> Analysis)
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith (Token -> Analysis) -> (Token -> Analysis) -> Token -> Analysis
forall a. (a -> Analysis) -> (a -> Analysis) -> a -> Analysis
composeAnalyzers CommandName
name Token -> Analysis
function Map CommandName (Token -> Analysis)
map


checkCommand :: Map.Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand :: Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand map :: Map CommandName (Token -> Analysis)
map t :: Token
t@(T_SimpleCommand id :: Id
id _ (cmd :: Token
cmd:rest :: [Token]
rest)) = Analysis -> Maybe Analysis -> Analysis
forall a. a -> Maybe a -> a
fromMaybe (() -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (Maybe Analysis -> Analysis) -> Maybe Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
    String
name <- Token -> Maybe String
getLiteralString Token
cmd
    Analysis -> Maybe Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (Analysis -> Maybe Analysis) -> Analysis -> Maybe Analysis
forall a b. (a -> b) -> a -> b
$
        if '/' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
name
        then
            (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Basename (String -> CommandName) -> String -> CommandName
forall a b. (a -> b) -> a -> b
$ String -> String
basename String
name) Map CommandName (Token -> Analysis)
map Token
t
        else do
            (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Exactly String
name) Map CommandName (Token -> Analysis)
map Token
t
            (Token -> Analysis)
-> CommandName
-> Map CommandName (Token -> Analysis)
-> Token
-> Analysis
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Token -> Analysis
forall b. b -> Analysis
nullCheck (String -> CommandName
Basename String
name) Map CommandName (Token -> Analysis)
map Token
t

  where
    basename :: String -> String
basename = String -> String
forall a. [a] -> [a]
reverse (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= '/') (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
forall a. [a] -> [a]
reverse
checkCommand _ _ = () -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return ()

getChecker :: [CommandCheck] -> Checker
getChecker :: [CommandCheck] -> Checker
getChecker list :: [CommandCheck]
list = Checker :: (Root -> Analysis) -> (Token -> Analysis) -> Checker
Checker {
    perScript :: Root -> Analysis
perScript = Analysis -> Root -> Analysis
forall a b. a -> b -> a
const (Analysis -> Root -> Analysis) -> Analysis -> Root -> Analysis
forall a b. (a -> b) -> a -> b
$ () -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (),
    perToken :: Token -> Analysis
perToken = Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand Map CommandName (Token -> Analysis)
map
    }
  where
    map :: Map CommandName (Token -> Analysis)
map = [CommandCheck] -> Map CommandName (Token -> Analysis)
buildCommandMap [CommandCheck]
list


checker :: Parameters -> Checker
checker :: Parameters -> Checker
checker params :: Parameters
params = [CommandCheck] -> Checker
getChecker [CommandCheck]
commandChecks

prop_checkTr1 :: Bool
prop_checkTr1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr "tr [a-f] [A-F]"
prop_checkTr2 :: Bool
prop_checkTr2 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr "tr 'a-z' 'A-Z'"
prop_checkTr2a :: Bool
prop_checkTr2a= CommandCheck -> String -> Bool
verify CommandCheck
checkTr "tr '[a-z]' '[A-Z]'"
prop_checkTr3 :: Bool
prop_checkTr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "tr -d '[:lower:]'"
prop_checkTr3a :: Bool
prop_checkTr3a= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "tr -d '[:upper:]'"
prop_checkTr3b :: Bool
prop_checkTr3b= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "tr -d '|/_[:upper:]'"
prop_checkTr4 :: Bool
prop_checkTr4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "ls [a-z]"
prop_checkTr5 :: Bool
prop_checkTr5 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr "tr foo bar"
prop_checkTr6 :: Bool
prop_checkTr6 = CommandCheck -> String -> Bool
verify CommandCheck
checkTr "tr 'hello' 'world'"
prop_checkTr8 :: Bool
prop_checkTr8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "tr aeiou _____"
prop_checkTr9 :: Bool
prop_checkTr9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "a-z n-za-m"
prop_checkTr10 :: Bool
prop_checkTr10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "tr --squeeze-repeats rl lr"
prop_checkTr11 :: Bool
prop_checkTr11= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "tr abc '[d*]'"
prop_checkTr12 :: Bool
prop_checkTr12= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTr "tr '[=e=]' 'e'"
checkTr :: CommandCheck
checkTr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "tr") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: Token -> m ()
f w :: Token
w | Token -> Bool
isGlob Token
w = -- The user will go [ab] -> '[ab]' -> 'ab'. Fixme?
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
w) 2060 "Quote parameters to tr to prevent glob expansion."
    f word :: Token
word =
      case Token -> Maybe String
getLiteralString Token
word of
        Just "a-z" -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) 2018 "Use '[:lower:]' to support accents and foreign alphabets."
        Just "A-Z" -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) 2019 "Use '[:upper:]' to support accents and foreign alphabets."
        Just s :: String
s -> do  -- Eliminate false positives by only looking for dupes in SET2?
          Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not ("-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
|| "[:" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s) Bool -> Bool -> Bool
&& String -> Bool
duplicated String
s) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) 2020 "tr replaces sets of chars, not words (mentioned due to duplicates)."
          Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ("[:" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
|| "[=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ("[" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
&& "]" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
s Bool -> Bool -> Bool
&& (String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
s Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 2) Bool -> Bool -> Bool
&& ('*' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
s)) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
              Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) 2021 "Don't use [] around classes in tr, it replaces literal square brackets."
        Nothing -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    duplicated :: String -> Bool
duplicated s :: String
s =
        let relevant :: String
relevant = (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter Char -> Bool
isAlpha String
s
        in String
relevant String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> String
forall a. Eq a => [a] -> [a]
nub String
relevant

prop_checkFindNameGlob1 :: Bool
prop_checkFindNameGlob1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindNameGlob "find / -name *.php"
prop_checkFindNameGlob2 :: Bool
prop_checkFindNameGlob2 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindNameGlob "find / -type f -ipath *(foo)"
prop_checkFindNameGlob3 :: Bool
prop_checkFindNameGlob3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindNameGlob "find * -name '*.php'"
checkFindNameGlob :: CommandCheck
checkFindNameGlob = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "find") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)  where
    acceptsGlob :: Maybe String -> Bool
acceptsGlob (Just s :: String
s) = String
s String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ "-ilname", "-iname", "-ipath", "-iregex", "-iwholename", "-lname", "-name", "-path", "-regex", "-wholename" ]
    acceptsGlob _ = Bool
False
    f :: [Token] -> m ()
f [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f [x :: Token
x] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f (a :: Token
a:b :: Token
b:r :: [Token]
r) = do
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Maybe String -> Bool
acceptsGlob (Token -> Maybe String
getLiteralString Token
a) Bool -> Bool -> Bool
&& Token -> Bool
isGlob Token
b) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
            let (Just s :: String
s) = Token -> Maybe String
getLiteralString Token
a
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
b) 2061 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Quote the parameter to " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ " so the shell won't interpret it."
        [Token] -> m ()
f (Token
bToken -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token]
r)


prop_checkNeedlessExpr :: Bool
prop_checkNeedlessExpr = CommandCheck -> String -> Bool
verify CommandCheck
checkNeedlessExpr "foo=$(expr 3 + 2)"
prop_checkNeedlessExpr2 :: Bool
prop_checkNeedlessExpr2 = CommandCheck -> String -> Bool
verify CommandCheck
checkNeedlessExpr "foo=`echo \\`expr 3 + 2\\``"
prop_checkNeedlessExpr3 :: Bool
prop_checkNeedlessExpr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNeedlessExpr "foo=$(expr foo : regex)"
prop_checkNeedlessExpr4 :: Bool
prop_checkNeedlessExpr4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNeedlessExpr "foo=$(expr foo \\< regex)"
checkNeedlessExpr :: CommandCheck
checkNeedlessExpr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "expr") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f where
    f :: Token -> f ()
f t :: Token
t =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
exceptions) ([Token] -> [String]
words ([Token] -> [String]) -> [Token] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t)) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) 2003
                "expr is antiquated. Consider rewriting this using $((..)), ${} or [[ ]]."
    -- These operators are hard to replicate in POSIX
    exceptions :: [String]
exceptions = [ ":", "<", ">", "<=", ">=" ]
    words :: [Token] -> [String]
words = (Token -> Maybe String) -> [Token] -> [String]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe String
getLiteralString


prop_checkGrepRe1 :: Bool
prop_checkGrepRe1 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe "cat foo | grep *.mp3"
prop_checkGrepRe2 :: Bool
prop_checkGrepRe2 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe "grep -Ev cow*test *.mp3"
prop_checkGrepRe3 :: Bool
prop_checkGrepRe3 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe "grep --regex=*.mp3 file"
prop_checkGrepRe4 :: Bool
prop_checkGrepRe4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep foo *.mp3"
prop_checkGrepRe5 :: Bool
prop_checkGrepRe5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep-v  --regex=moo *"
prop_checkGrepRe6 :: Bool
prop_checkGrepRe6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep foo \\*.mp3"
prop_checkGrepRe7 :: Bool
prop_checkGrepRe7 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe "grep *foo* file"
prop_checkGrepRe8 :: Bool
prop_checkGrepRe8 = CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe "ls | grep foo*.jpg"
prop_checkGrepRe9 :: Bool
prop_checkGrepRe9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep '[0-9]*' file"
prop_checkGrepRe10 :: Bool
prop_checkGrepRe10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep '^aa*' file"
prop_checkGrepRe11 :: Bool
prop_checkGrepRe11= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep --include=*.png foo"
prop_checkGrepRe12 :: Bool
prop_checkGrepRe12= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep -F 'Foo*' file"
prop_checkGrepRe13 :: Bool
prop_checkGrepRe13= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep -- -foo bar*"
prop_checkGrepRe14 :: Bool
prop_checkGrepRe14= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep -e -foo bar*"
prop_checkGrepRe15 :: Bool
prop_checkGrepRe15= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep --regex -foo bar*"
prop_checkGrepRe16 :: Bool
prop_checkGrepRe16= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep --include 'Foo*' file"
prop_checkGrepRe17 :: Bool
prop_checkGrepRe17= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep --exclude 'Foo*' file"
prop_checkGrepRe18 :: Bool
prop_checkGrepRe18= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep --exclude-dir 'Foo*' file"
prop_checkGrepRe19 :: Bool
prop_checkGrepRe19= CommandCheck -> String -> Bool
verify CommandCheck
checkGrepRe "grep -- 'Foo*' file"
prop_checkGrepRe20 :: Bool
prop_checkGrepRe20= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep --fixed-strings 'Foo*' file"
prop_checkGrepRe21 :: Bool
prop_checkGrepRe21= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep -o 'x*' file"
prop_checkGrepRe22 :: Bool
prop_checkGrepRe22= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep --only-matching 'x*' file"
prop_checkGrepRe23 :: Bool
prop_checkGrepRe23= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkGrepRe "grep '.*' file"

checkGrepRe :: CommandCheck
checkGrepRe = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "grep") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check where
    check :: Token -> m ()
check cmd :: Token
cmd = Token -> [Token] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
f Token
cmd (Token -> [Token]
arguments Token
cmd)
    -- --regex=*(extglob) doesn't work. Fixme?
    skippable :: Maybe String -> Bool
skippable (Just s :: String
s) = Bool -> Bool
not ("--regex=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s) Bool -> Bool -> Bool
&& "-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s
    skippable _ = Bool
False
    f :: Token -> [Token] -> m ()
f _ [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f cmd :: Token
cmd (x :: Token
x:r :: [Token]
r) =
        let str :: Maybe String
str = (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt (Maybe String -> Token -> Maybe String
forall a b. a -> b -> a
const (Maybe String -> Token -> Maybe String)
-> Maybe String -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return "_") Token
x
        in
            if Maybe String
str Maybe String -> [Maybe String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String -> Maybe String
forall a. a -> Maybe a
Just "--", String -> Maybe String
forall a. a -> Maybe a
Just "-e", String -> Maybe String
forall a. a -> Maybe a
Just "--regex"]
            then Token -> [Token] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
checkRE Token
cmd [Token]
r -- Regex is *after* this
            else
                if Maybe String -> Bool
skippable Maybe String
str
                then Token -> [Token] -> m ()
f Token
cmd [Token]
r           -- Regex is elsewhere
                else Token -> [Token] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> [Token] -> m ()
checkRE Token
cmd (Token
xToken -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token]
r) -- Regex is this

    checkRE :: Token -> [Token] -> m ()
checkRE _ [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkRE cmd :: Token
cmd (re :: Token
re:_) = do
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
re) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
re) 2062 "Quote the grep pattern so the shell won't interpret it."

        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) [String]
grepGlobFlags) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
            let string :: String
string = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
re
            if String -> Bool
isConfusedGlobRegex String
string then
                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
re) 2063 "Grep uses regex, but this looks like a glob."
              else Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
                Char
char <- String -> Maybe Char
getSuspiciousRegexWildcard String
string
                m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
re) 2022 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                    "Note that unlike globs, " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
char] String -> String -> String
forall a. [a] -> [a] -> [a]
++ "* here matches '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
char, Char
char, Char
char] String -> String -> String
forall a. [a] -> [a] -> [a]
++ "' but not '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Char -> String
wordStartingWith Char
char String -> String -> String
forall a. [a] -> [a] -> [a]
++ "'."
      where
        flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String]) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
cmd
        grepGlobFlags :: [String]
grepGlobFlags = ["fixed-strings", "F", "include", "exclude", "exclude-dir", "o", "only-matching"]

    wordStartingWith :: Char -> String
wordStartingWith c :: Char
c =
        [String] -> String
forall a. [a] -> a
head ([String] -> String)
-> ([String] -> [String]) -> [String] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter ([Char
c] String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`) ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ [String]
candidates
      where
        candidates :: [String]
candidates =
            [String]
sampleWords [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ (String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (\(x :: Char
x:r :: String
r) -> Char -> Char
toUpper Char
x Char -> String -> String
forall a. a -> [a] -> [a]
: String
r) [String]
sampleWords [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [Char
cChar -> String -> String
forall a. a -> [a] -> [a]
:"test"]

    getSuspiciousRegexWildcard :: String -> Maybe Char
getSuspiciousRegexWildcard str :: String
str =
        if Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ String
str String -> Regex -> Bool
`matches` Regex
contra
        then do
            [String]
match <- Regex -> String -> Maybe [String]
matchRegex Regex
suspicious String
str
            String
str <- [String]
match [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! 0
            String
str String -> Int -> Maybe Char
forall a. [a] -> Int -> Maybe a
!!! 0
        else
            String -> Maybe Char
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "looks good"
      where
        suspicious :: Regex
suspicious = String -> Regex
mkRegex "([A-Za-z1-9])\\*"
        contra :: Regex
contra = String -> Regex
mkRegex "[^a-zA-Z1-9]\\*|[][^$+\\\\]"


prop_checkTrapQuotes1 :: Bool
prop_checkTrapQuotes1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTrapQuotes "trap \"echo $num\" INT"
prop_checkTrapQuotes1a :: Bool
prop_checkTrapQuotes1a= CommandCheck -> String -> Bool
verify CommandCheck
checkTrapQuotes "trap \"echo `ls`\" INT"
prop_checkTrapQuotes2 :: Bool
prop_checkTrapQuotes2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTrapQuotes "trap 'echo $num' INT"
prop_checkTrapQuotes3 :: Bool
prop_checkTrapQuotes3 = CommandCheck -> String -> Bool
verify CommandCheck
checkTrapQuotes "trap \"echo $((1+num))\" EXIT DEBUG"
checkTrapQuotes :: CommandCheck
checkTrapQuotes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "trap") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
    f :: [Token] -> m ()
f (x :: Token
x:_) = Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkTrap Token
x
    f _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkTrap :: Token -> m ()
checkTrap (T_NormalWord _ [T_DoubleQuoted _ rs :: [Token]
rs]) = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkExpansions [Token]
rs
    checkTrap _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    warning :: Id -> m ()
warning id :: Id
id = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id 2064 "Use single quotes, otherwise this expands now rather than when signalled."
    checkExpansions :: Token -> m ()
checkExpansions (T_DollarExpansion id :: Id
id _) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions (T_Backticked id :: Id
id _) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions (T_DollarBraced id :: Id
id _ _) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions (T_DollarArithmetic id :: Id
id _) = Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warning Id
id
    checkExpansions _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkReturn1 :: Bool
prop_checkReturn1 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn "return"
prop_checkReturn2 :: Bool
prop_checkReturn2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn "return 1"
prop_checkReturn3 :: Bool
prop_checkReturn3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn "return $var"
prop_checkReturn4 :: Bool
prop_checkReturn4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReturn "return $((a|b))"
prop_checkReturn5 :: Bool
prop_checkReturn5 = CommandCheck -> String -> Bool
verify CommandCheck
checkReturn "return -1"
prop_checkReturn6 :: Bool
prop_checkReturn6 = CommandCheck -> String -> Bool
verify CommandCheck
checkReturn "return 1000"
prop_checkReturn7 :: Bool
prop_checkReturn7 = CommandCheck -> String -> Bool
verify CommandCheck
checkReturn "return 'hello world'"
checkReturn :: CommandCheck
checkReturn = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "return") ((Id -> Analysis) -> (Id -> Analysis) -> Token -> Analysis
forall (f :: * -> *).
Monad f =>
(Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit
        (\c :: Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c 2151 "Only one integer 0-255 can be returned. Use stdout for other data.")
        (\c :: Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c 2152 "Can only return 0-255. Other data should be written to stdout."))

prop_checkExit1 :: Bool
prop_checkExit1 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit "exit"
prop_checkExit2 :: Bool
prop_checkExit2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit "exit 1"
prop_checkExit3 :: Bool
prop_checkExit3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit "exit $var"
prop_checkExit4 :: Bool
prop_checkExit4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExit "exit $((a|b))"
prop_checkExit5 :: Bool
prop_checkExit5 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit "exit -1"
prop_checkExit6 :: Bool
prop_checkExit6 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit "exit 1000"
prop_checkExit7 :: Bool
prop_checkExit7 = CommandCheck -> String -> Bool
verify CommandCheck
checkExit "exit 'hello world'"
checkExit :: CommandCheck
checkExit = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "exit") ((Id -> Analysis) -> (Id -> Analysis) -> Token -> Analysis
forall (f :: * -> *).
Monad f =>
(Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit
        (\c :: Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c 2241 "The exit status can only be one integer 0-255. Use stdout for other data.")
        (\c :: Id
c -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
c 2242 "Can only exit with status 0-255. Other data should be written to stdout/stderr."))

returnOrExit :: (Id -> f ()) -> (Id -> f ()) -> Token -> f ()
returnOrExit multi :: Id -> f ()
multi invalid :: Id -> f ()
invalid = ([Token] -> f ()
f ([Token] -> f ()) -> (Token -> [Token]) -> Token -> f ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> f ()
f (first :: Token
first:second :: Token
second:_) =
        Id -> f ()
multi (Token -> Id
getId Token
first)
    f [value :: Token
value] =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isInvalid (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> String
literal Token
value) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> f ()
invalid (Token -> Id
getId Token
value)
    f _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isInvalid :: String -> Bool
isInvalid s :: String
s = String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "" Bool -> Bool -> Bool
|| (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isDigit) String
s Bool -> Bool -> Bool
|| String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
s Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 5
        Bool -> Bool -> Bool
|| let value :: Code
value = (String -> Code
forall a. Read a => String -> a
read String
s :: Integer) in Code
value Code -> Code -> Bool
forall a. Ord a => a -> a -> Bool
> 255

    literal :: Token -> String
literal token :: Token
token = Maybe String -> String
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt Token -> Maybe String
forall (m :: * -> *). Monad m => Token -> m String
lit Token
token
    lit :: Token -> m String
lit (T_DollarBraced {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "0"
    lit (T_DollarArithmetic {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "0"
    lit (T_DollarExpansion {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "0"
    lit (T_Backticked {}) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "0"
    lit _ = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return "WTF"


prop_checkFindExecWithSingleArgument1 :: Bool
prop_checkFindExecWithSingleArgument1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindExecWithSingleArgument "find . -exec 'cat {} | wc -l' \\;"
prop_checkFindExecWithSingleArgument2 :: Bool
prop_checkFindExecWithSingleArgument2 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindExecWithSingleArgument "find . -execdir 'cat {} | wc -l' +"
prop_checkFindExecWithSingleArgument3 :: Bool
prop_checkFindExecWithSingleArgument3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindExecWithSingleArgument "find . -exec wc -l {} \\;"
checkFindExecWithSingleArgument :: CommandCheck
checkFindExecWithSingleArgument = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "find") ([Token] -> Analysis
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> Analysis
f = RWST Parameters [TokenComment] Cache Identity [()] -> Analysis
forall (f :: * -> *) a. Functor f => f a -> f ()
void (RWST Parameters [TokenComment] Cache Identity [()] -> Analysis)
-> ([Token] -> RWST Parameters [TokenComment] Cache Identity [()])
-> [Token]
-> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Analysis] -> RWST Parameters [TokenComment] Cache Identity [()]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence ([Analysis] -> RWST Parameters [TokenComment] Cache Identity [()])
-> ([Token] -> [Analysis])
-> [Token]
-> RWST Parameters [TokenComment] Cache Identity [()]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Token] -> Maybe Analysis) -> [[Token]] -> [Analysis]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe [Token] -> Maybe Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> Maybe (m ())
check ([[Token]] -> [Analysis])
-> ([Token] -> [[Token]]) -> [Token] -> [Analysis]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [[Token]]
forall a. [a] -> [[a]]
tails
    check :: [Token] -> Maybe (m ())
check (exec :: Token
exec:arg :: Token
arg:term :: Token
term:_) = do
        String
execS <- Token -> Maybe String
getLiteralString Token
exec
        String
termS <- Token -> Maybe String
getLiteralString Token
term
        String
cmdS <- (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt (Maybe String -> Token -> Maybe String
forall a b. a -> b -> a
const (Maybe String -> Token -> Maybe String)
-> Maybe String -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return " ") Token
arg

        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
execS String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["-exec", "-execdir"] Bool -> Bool -> Bool
&& String
termS String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [";", "+"]
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
cmdS String -> Regex -> Bool
`matches` Regex
commandRegex
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
exec) 2150 "-exec does not invoke a shell. Rewrite or use -exec sh -c .. ."
    check _ = Maybe (m ())
forall a. Maybe a
Nothing
    commandRegex :: Regex
commandRegex = String -> Regex
mkRegex "[ |;]"


prop_checkUnusedEchoEscapes1 :: Bool
prop_checkUnusedEchoEscapes1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnusedEchoEscapes "echo 'foo\\nbar\\n'"
prop_checkUnusedEchoEscapes2 :: Bool
prop_checkUnusedEchoEscapes2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes "echo -e 'foi\\nbar'"
prop_checkUnusedEchoEscapes3 :: Bool
prop_checkUnusedEchoEscapes3 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnusedEchoEscapes "echo \"n:\\t42\""
prop_checkUnusedEchoEscapes4 :: Bool
prop_checkUnusedEchoEscapes4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes "echo lol"
prop_checkUnusedEchoEscapes5 :: Bool
prop_checkUnusedEchoEscapes5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnusedEchoEscapes "echo -n -e '\n'"
checkUnusedEchoEscapes :: CommandCheck
checkUnusedEchoEscapes = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "echo") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    hasEscapes :: Regex
hasEscapes = String -> Regex
mkRegex "\\\\[rnt]"
    f :: Token -> m ()
f cmd :: Token
cmd =
        [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Bash, Shell
Ksh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token
cmd Token -> String -> Bool
`hasFlag` "e") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
examine ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
cmd

    examine :: Token -> f ()
examine token :: Token
token = do
        let str :: String
str = Token -> String
onlyLiteralString Token
token
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
str String -> Regex -> Bool
`matches` Regex
hasEscapes) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
token) 2028 "echo may not expand escape sequences. Use printf."


prop_checkInjectableFindSh1 :: Bool
prop_checkInjectableFindSh1 = CommandCheck -> String -> Bool
verify CommandCheck
checkInjectableFindSh "find . -exec sh -c 'echo {}' \\;"
prop_checkInjectableFindSh2 :: Bool
prop_checkInjectableFindSh2 = CommandCheck -> String -> Bool
verify CommandCheck
checkInjectableFindSh "find . -execdir bash -c 'rm \"{}\"' ';'"
prop_checkInjectableFindSh3 :: Bool
prop_checkInjectableFindSh3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkInjectableFindSh "find . -exec sh -c 'rm \"$@\"' _ {} \\;"
checkInjectableFindSh :: CommandCheck
checkInjectableFindSh = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "find") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: [Token] -> m ()
check args :: [Token]
args = do
        let idStrings :: [(Id, String)]
idStrings = (Token -> (Id, String)) -> [Token] -> [(Id, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: Token
x -> (Token -> Id
getId Token
x, Token -> String
onlyLiteralString Token
x)) [Token]
args
        [String -> Bool] -> [(Id, String)] -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[String -> Bool] -> [(Id, String)] -> m ()
match [String -> Bool]
pattern [(Id, String)]
idStrings

    match :: [String -> Bool] -> [(Id, String)] -> m ()
match _ [] = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    match [] (next :: (Id, String)
next:_) = (Id, String) -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
(Id, String) -> f ()
action (Id, String)
next
    match (p :: String -> Bool
p:tests :: [String -> Bool]
tests) ((id :: Id
id, arg :: String
arg):args :: [(Id, String)]
args) = do
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
p String
arg) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ [String -> Bool] -> [(Id, String)] -> m ()
match [String -> Bool]
tests [(Id, String)]
args
        [String -> Bool] -> [(Id, String)] -> m ()
match (String -> Bool
p(String -> Bool) -> [String -> Bool] -> [String -> Bool]
forall a. a -> [a] -> [a]
:[String -> Bool]
tests) [(Id, String)]
args

    pattern :: [String -> Bool]
pattern = [
        (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["-exec", "-execdir"]),
        (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["sh", "bash", "dash", "ksh"]),
        (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "-c")
        ]
    action :: (Id, String) -> f ()
action (id :: Id
id, arg :: String
arg) =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ("{}" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
arg) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id 2156 "Injecting filenames is fragile and insecure. Use parameters."


prop_checkFindActionPrecedence1 :: Bool
prop_checkFindActionPrecedence1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au' -exec rm {} +"
prop_checkFindActionPrecedence2 :: Bool
prop_checkFindActionPrecedence2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindActionPrecedence "find . -name '*.wav' -o \\( -name '*.au' -exec rm {} + \\)"
prop_checkFindActionPrecedence3 :: Bool
prop_checkFindActionPrecedence3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindActionPrecedence "find . -name '*.wav' -o -name '*.au'"
checkFindActionPrecedence :: CommandCheck
checkFindActionPrecedence = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "find") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    pattern :: [Token -> Bool]
pattern = [Token -> Bool
isMatch, Bool -> Token -> Bool
forall a b. a -> b -> a
const Bool
True, [String] -> Token -> Bool
forall (t :: * -> *). Foldable t => t String -> Token -> Bool
isParam ["-o", "-or"], Token -> Bool
isMatch, Bool -> Token -> Bool
forall a b. a -> b -> a
const Bool
True, Token -> Bool
isAction]
    f :: [Token] -> m ()
f list :: [Token]
list | [Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
list Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< [Token -> Bool] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token -> Bool]
pattern = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    f list :: [Token]
list@(_:rest :: [Token]
rest) =
        if [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
and (((Token -> Bool) -> Token -> Bool)
-> [Token -> Bool] -> [Token] -> [Bool]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (Token -> Bool) -> Token -> Bool
forall a b. (a -> b) -> a -> b
($) [Token -> Bool]
pattern [Token]
list)
        then Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnFor ([Token]
list [Token] -> Int -> Token
forall a. [a] -> Int -> a
!! ([Token -> Bool] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token -> Bool]
pattern Int -> Int -> Int
forall a. Num a => a -> a -> a
- 1))
        else [Token] -> m ()
f [Token]
rest
    isMatch :: Token -> Bool
isMatch = [String] -> Token -> Bool
forall (t :: * -> *). Foldable t => t String -> Token -> Bool
isParam [ "-name", "-regex", "-iname", "-iregex", "-wholename", "-iwholename" ]
    isAction :: Token -> Bool
isAction = [String] -> Token -> Bool
forall (t :: * -> *). Foldable t => t String -> Token -> Bool
isParam [ "-exec", "-execdir", "-delete", "-print", "-print0", "-fls", "-fprint", "-fprint0", "-fprintf", "-ls", "-ok", "-okdir", "-printf" ]
    isParam :: t String -> Token -> Bool
isParam strs :: t String
strs t :: Token
t = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        String
param <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String
param String -> t String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t String
strs
    warnFor :: Token -> m ()
warnFor t :: Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) 2146 "This action ignores everything before the -o. Use \\( \\) to group."


prop_checkMkdirDashPM0 :: Bool
prop_checkMkdirDashPM0 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 a/b"
prop_checkMkdirDashPM1 :: Bool
prop_checkMkdirDashPM1 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir -pm 0755 $dir"
prop_checkMkdirDashPM2 :: Bool
prop_checkMkdirDashPM2 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir -vpm 0755 a/b"
prop_checkMkdirDashPM3 :: Bool
prop_checkMkdirDashPM3 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir -pm 0755 -v a/b"
prop_checkMkdirDashPM4 :: Bool
prop_checkMkdirDashPM4 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir --parents --mode=0755 a/b"
prop_checkMkdirDashPM5 :: Bool
prop_checkMkdirDashPM5 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir --parents --mode 0755 a/b"
prop_checkMkdirDashPM6 :: Bool
prop_checkMkdirDashPM6 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir -p --mode=0755 a/b"
prop_checkMkdirDashPM7 :: Bool
prop_checkMkdirDashPM7 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir --parents -m 0755 a/b"
prop_checkMkdirDashPM8 :: Bool
prop_checkMkdirDashPM8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -p a/b"
prop_checkMkdirDashPM9 :: Bool
prop_checkMkdirDashPM9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -m 0755 a/b"
prop_checkMkdirDashPM10 :: Bool
prop_checkMkdirDashPM10 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir a/b"
prop_checkMkdirDashPM11 :: Bool
prop_checkMkdirDashPM11 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir --parents a/b"
prop_checkMkdirDashPM12 :: Bool
prop_checkMkdirDashPM12 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir --mode=0755 a/b"
prop_checkMkdirDashPM13 :: Bool
prop_checkMkdirDashPM13 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir_func -pm 0755 a/b"
prop_checkMkdirDashPM14 :: Bool
prop_checkMkdirDashPM14 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 singlelevel"
prop_checkMkdirDashPM15 :: Bool
prop_checkMkdirDashPM15 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 ../bin"
prop_checkMkdirDashPM16 :: Bool
prop_checkMkdirDashPM16 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 ../bin/laden"
prop_checkMkdirDashPM17 :: Bool
prop_checkMkdirDashPM17 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 ./bin"
prop_checkMkdirDashPM18 :: Bool
prop_checkMkdirDashPM18 = CommandCheck -> String -> Bool
verify CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 ./bin/laden"
prop_checkMkdirDashPM19 :: Bool
prop_checkMkdirDashPM19 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 ./../bin"
prop_checkMkdirDashPM20 :: Bool
prop_checkMkdirDashPM20 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 .././bin"
prop_checkMkdirDashPM21 :: Bool
prop_checkMkdirDashPM21 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMkdirDashPM "mkdir -p -m 0755 ../../bin"
checkMkdirDashPM :: CommandCheck
checkMkdirDashPM = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "mkdir") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check
  where
    check :: Token -> m ()
check t :: Token
t = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        let flags :: [(Token, String)]
flags = Token -> [(Token, String)]
getAllFlags Token
t
        (Token, String)
dashP <- ((Token, String) -> Bool)
-> [(Token, String)] -> Maybe (Token, String)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((\f :: String
f -> String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "p" Bool -> Bool -> Bool
|| String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "parents") (String -> Bool)
-> ((Token, String) -> String) -> (Token, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, String) -> String
forall a b. (a, b) -> b
snd) [(Token, String)]
flags
        (Token, String)
dashM <- ((Token, String) -> Bool)
-> [(Token, String)] -> Maybe (Token, String)
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((\f :: String
f -> String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "m" Bool -> Bool -> Bool
|| String
f String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "mode") (String -> Bool)
-> ((Token, String) -> String) -> (Token, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, String) -> String
forall a b. (a, b) -> b
snd) [(Token, String)]
flags
        -- mkdir -pm 0700 dir  is fine, so is ../dir, but dir/subdir is not.
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
couldHaveSubdirs (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop 1 ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t)
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ (Token, String) -> Token
forall a b. (a, b) -> a
fst (Token, String)
dashM) 2174 "When used with -p, -m only applies to the deepest directory."
    couldHaveSubdirs :: Token -> Bool
couldHaveSubdirs t :: Token
t = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
True (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        String
name <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ '/' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
name Bool -> Bool -> Bool
&& Bool -> Bool
not (String
name String -> Regex -> Bool
`matches` Regex
re)
    re :: Regex
re = String -> Regex
mkRegex "^(\\.\\.?\\/)+[^/]+$"


prop_checkNonportableSignals1 :: Bool
prop_checkNonportableSignals1 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals "trap f 8"
prop_checkNonportableSignals2 :: Bool
prop_checkNonportableSignals2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals "trap f 0"
prop_checkNonportableSignals3 :: Bool
prop_checkNonportableSignals3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals "trap f 14"
prop_checkNonportableSignals4 :: Bool
prop_checkNonportableSignals4 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals "trap f SIGKILL"
prop_checkNonportableSignals5 :: Bool
prop_checkNonportableSignals5 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals "trap f 9"
prop_checkNonportableSignals6 :: Bool
prop_checkNonportableSignals6 = CommandCheck -> String -> Bool
verify CommandCheck
checkNonportableSignals "trap f stop"
prop_checkNonportableSignals7 :: Bool
prop_checkNonportableSignals7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkNonportableSignals "trap 'stop' int"
checkNonportableSignals :: CommandCheck
checkNonportableSignals = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "trap") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> f ()
f args :: [Token]
args = case [Token]
args of
        first :: Token
first:rest :: [Token]
rest -> Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> Bool
isFlag Token
first) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ (Token -> f ()) -> [Token] -> f ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check [Token]
rest
        _ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check :: Token -> m ()
check param :: Token
param = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
str <- Token -> Maybe String
getLiteralString Token
param
        let id :: Id
id = Token -> Id
getId Token
param
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ [m ()] -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ ([m ()] -> m ()) -> [m ()] -> m ()
forall a b. (a -> b) -> a -> b
$ ((Id -> String -> Maybe (m ())) -> Maybe (m ()))
-> [Id -> String -> Maybe (m ())] -> [m ()]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\f :: Id -> String -> Maybe (m ())
f -> Id -> String -> Maybe (m ())
f Id
id String
str) [
            Id -> String -> Maybe (m ())
forall (m :: * -> *) (m :: * -> *).
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> String -> m (m ())
checkNumeric,
            Id -> String -> Maybe (m ())
forall (m :: * -> *) (m :: * -> *).
(Alternative m, MonadWriter [TokenComment] m, Monad m) =>
Id -> String -> m (m ())
checkUntrappable
            ]

    checkNumeric :: Id -> String -> m (m ())
checkNumeric id :: Id
id str :: String
str = do
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
str)
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
str
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "0" -- POSIX exit trap
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` ["1", "2", "3", "6", "9", "14", "15" ] -- XSI
        m () -> m (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> m (m ())) -> m () -> m (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id 2172
            "Trapping signals by number is not well defined. Prefer signal names."

    checkUntrappable :: Id -> String -> m (m ())
checkUntrappable id :: Id
id str :: String
str = do
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["kill", "9", "sigkill", "stop", "sigstop"]
        m () -> m (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> m (m ())) -> m () -> m (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id 2173
            "SIGKILL/SIGSTOP can not be trapped."


prop_checkInteractiveSu1 :: Bool
prop_checkInteractiveSu1 = CommandCheck -> String -> Bool
verify CommandCheck
checkInteractiveSu "su; rm file; su $USER"
prop_checkInteractiveSu2 :: Bool
prop_checkInteractiveSu2 = CommandCheck -> String -> Bool
verify CommandCheck
checkInteractiveSu "su foo; something; exit"
prop_checkInteractiveSu3 :: Bool
prop_checkInteractiveSu3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkInteractiveSu "echo rm | su foo"
prop_checkInteractiveSu4 :: Bool
prop_checkInteractiveSu4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkInteractiveSu "su root < script"
checkInteractiveSu :: CommandCheck
checkInteractiveSu = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "su") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> f ()
f cmd :: Token
cmd = Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (Token -> [Token]
arguments Token
cmd) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 1) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ do
        [Token]
path <- Token -> f [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
cmd
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
undirected [Token]
path) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
cmd) 2117
                "To run commands as another user, use su -c or sudo."

    undirected :: Token -> Bool
undirected (T_Pipeline _ _ l :: [Token]
l) = [Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
l Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 1
    -- This should really just be modifications to stdin, but meh
    undirected (T_Redirecting _ list :: [Token]
list _) = [Token] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
list
    undirected _ = Bool
True


-- This is hard to get right without properly parsing ssh args
prop_checkSshCmdStr1 :: Bool
prop_checkSshCmdStr1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSshCommandString "ssh host \"echo $PS1\""
prop_checkSshCmdStr2 :: Bool
prop_checkSshCmdStr2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString "ssh host \"ls foo\""
prop_checkSshCmdStr3 :: Bool
prop_checkSshCmdStr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString "ssh \"$host\""
prop_checkSshCmdStr4 :: Bool
prop_checkSshCmdStr4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSshCommandString "ssh -i key \"$host\""
checkSshCommandString :: CommandCheck
checkSshCommandString = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "ssh") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    isOption :: Token -> Bool
isOption x :: Token
x = "-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
x)
    f :: [Token] -> m ()
f args :: [Token]
args =
        case (Token -> Bool) -> [Token] -> ([Token], [Token])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition Token -> Bool
isOption [Token]
args of
            ([], hostport :: Token
hostport:r :: [Token]
r@(_:_)) -> Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArg (Token -> m ()) -> Token -> m ()
forall a b. (a -> b) -> a -> b
$ [Token] -> Token
forall a. [a] -> a
last [Token]
r
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkArg :: Token -> m ()
checkArg (T_NormalWord _ [T_DoubleQuoted id :: Id
id parts :: [Token]
parts]) =
        case (Token -> Bool) -> [Token] -> [Token]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isConstant) [Token]
parts of
            [] -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
            (x :: Token
x:_) -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
x) 2029
                "Note that, unescaped, this expands on the client side."
    checkArg _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkPrintfVar1 :: Bool
prop_checkPrintfVar1 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf \"Lol: $s\""
prop_checkPrintfVar2 :: Bool
prop_checkPrintfVar2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf 'Lol: $s'"
prop_checkPrintfVar3 :: Bool
prop_checkPrintfVar3 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf -v cow $(cmd)"
prop_checkPrintfVar4 :: Bool
prop_checkPrintfVar4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf \"%${count}s\" var"
prop_checkPrintfVar5 :: Bool
prop_checkPrintfVar5 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf '%s %s %s' foo bar"
prop_checkPrintfVar6 :: Bool
prop_checkPrintfVar6 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf foo bar baz"
prop_checkPrintfVar7 :: Bool
prop_checkPrintfVar7 = CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf -- foo bar baz"
prop_checkPrintfVar8 :: Bool
prop_checkPrintfVar8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%s %s %s' \"${var[@]}\""
prop_checkPrintfVar9 :: Bool
prop_checkPrintfVar9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%s %s %s\\n' *.png"
prop_checkPrintfVar10 :: Bool
prop_checkPrintfVar10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%s %s %s' foo bar baz"
prop_checkPrintfVar11 :: Bool
prop_checkPrintfVar11= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%(%s%s)T' -1"
prop_checkPrintfVar12 :: Bool
prop_checkPrintfVar12= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf '%s %s\\n' 1 2 3"
prop_checkPrintfVar13 :: Bool
prop_checkPrintfVar13= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%s %s\\n' 1 2 3 4"
prop_checkPrintfVar14 :: Bool
prop_checkPrintfVar14= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf '%*s\\n' 1"
prop_checkPrintfVar15 :: Bool
prop_checkPrintfVar15= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%*s\\n' 1 2"
prop_checkPrintfVar16 :: Bool
prop_checkPrintfVar16= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf $'string'"
prop_checkPrintfVar17 :: Bool
prop_checkPrintfVar17= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf '%-*s\\n' 1"
prop_checkPrintfVar18 :: Bool
prop_checkPrintfVar18= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%-*s\\n' 1 2"
prop_checkPrintfVar19 :: Bool
prop_checkPrintfVar19= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%(%s)T'"
prop_checkPrintfVar20 :: Bool
prop_checkPrintfVar20= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkPrintfVar "printf '%d %(%s)T' 42"
prop_checkPrintfVar21 :: Bool
prop_checkPrintfVar21= CommandCheck -> String -> Bool
verify CommandCheck
checkPrintfVar "printf '%d %(%s)T'"
checkPrintfVar :: CommandCheck
checkPrintfVar = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "printf") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
    f :: [Token] -> m ()
f (doubledash :: Token
doubledash:rest :: [Token]
rest) | Token -> Maybe String
getLiteralString Token
doubledash Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just "--" = [Token] -> m ()
f [Token]
rest
    f (dashv :: Token
dashv:var :: Token
var:rest :: [Token]
rest) | Token -> Maybe String
getLiteralString Token
dashv Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just "-v" = [Token] -> m ()
f [Token]
rest
    f (format :: Token
format:params :: [Token]
params) = Token -> [Token] -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadWriter [TokenComment] m, Foldable t) =>
Token -> t Token -> m ()
check Token
format [Token]
params
    f _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check :: Token -> t Token -> m ()
check format :: Token
format more :: t Token
more = do
        m () -> Maybe (m ()) -> m ()
forall a. a -> Maybe a -> a
fromMaybe (() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            String
string <- Token -> Maybe String
getLiteralString Token
format
            let formats :: String
formats = String -> String
getPrintfFormats String
string
            let formatCount :: Int
formatCount = String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
formats
            let argCount :: Int
argCount = t Token -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length t Token
more

            m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
                case () of
                    () | Int
argCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0 Bool -> Bool -> Bool
&& Int
formatCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0 ->
                        () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return () -- This is fine
                    () | Int
formatCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0 Bool -> Bool -> Bool
&& Int
argCount Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 ->
                        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
format) 2182
                            "This printf format string has no variables. Other arguments are ignored."
                    () | (Token -> Bool) -> t Token -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
mayBecomeMultipleArgs t Token
more ->
                        () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return () -- We don't know so trust the user
                    () | Int
argCount Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
formatCount Bool -> Bool -> Bool
&& String -> Int -> Bool
onlyTrailingTs String
formats Int
argCount ->
                        () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return () -- Allow trailing %()Ts since they use the current time
                    () | Int
argCount Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 Bool -> Bool -> Bool
&& Int
argCount Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
formatCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0 ->
                        () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return () -- Great: a suitable number of arguments
                    () ->
                        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
format) 2183 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                            "This format string has " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
formatCount String -> String -> String
forall a. [a] -> [a] -> [a]
++ " variables, but is passed " String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
argCount String -> String -> String
forall a. [a] -> [a] -> [a]
++ " arguments."

        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ('%' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
format) Bool -> Bool -> Bool
|| Token -> Bool
isLiteral Token
format) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
          Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
format) 2059
              "Don't use variables in the printf format string. Use printf \"..%s..\" \"$foo\"."
      where
        onlyTrailingTs :: String -> Int -> Bool
onlyTrailingTs format :: String
format argCount :: Int
argCount =
            (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== 'T') (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
argCount String
format


prop_checkGetPrintfFormats1 :: Bool
prop_checkGetPrintfFormats1 = String -> String
getPrintfFormats "%s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "s"
prop_checkGetPrintfFormats2 :: Bool
prop_checkGetPrintfFormats2 = String -> String
getPrintfFormats "%0*s" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "*s"
prop_checkGetPrintfFormats3 :: Bool
prop_checkGetPrintfFormats3 = String -> String
getPrintfFormats "%(%s)T" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "T"
prop_checkGetPrintfFormats4 :: Bool
prop_checkGetPrintfFormats4 = String -> String
getPrintfFormats "%d%%%(%s)T" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "dT"
prop_checkGetPrintfFormats5 :: Bool
prop_checkGetPrintfFormats5 = String -> String
getPrintfFormats "%bPassed: %d, %bFailed: %d%b, Skipped: %d, %bErrored: %d%b\\n" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "bdbdbdbdb"
getPrintfFormats :: String -> String
getPrintfFormats = String -> String
getFormats
  where
    -- Get the arguments in the string as a string of type characters,
    -- e.g. "Hello %s" -> "s" and "%(%s)T %0*d\n" -> "T*d"
    getFormats :: String -> String
    getFormats :: String -> String
getFormats string :: String
string =
        case String
string of
            '%':'%':rest :: String
rest -> String -> String
getFormats String
rest
            '%':'(':rest :: String
rest ->
                case (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= ')') String
rest of
                    ')':c :: Char
c:trailing :: String
trailing -> Char
c Char -> String -> String
forall a. a -> [a] -> [a]
: String -> String
getFormats String
trailing
                    _ -> ""
            '%':rest :: String
rest -> String -> String
regexBasedGetFormats String
rest
            _:rest :: String
rest -> String -> String
getFormats String
rest
            [] -> ""

    regexBasedGetFormats :: String -> String
regexBasedGetFormats rest :: String
rest =
        case Regex -> String -> Maybe [String]
matchRegex Regex
re String
rest of
            Just [width :: String
width, precision :: String
precision, typ :: String
typ, rest :: String
rest] ->
                (if String
width String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "*" then "*" else "") String -> String -> String
forall a. [a] -> [a] -> [a]
++
                (if String
precision String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "*" then "*" else "") String -> String -> String
forall a. [a] -> [a] -> [a]
++
                String
typ String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
getFormats String
rest
            Nothing -> Int -> String -> String
forall a. Int -> [a] -> [a]
take 1 String
rest String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
getFormats String
rest
      where
        -- constructed based on specifications in "man printf"
        re :: Regex
re = String -> Regex
mkRegex "#?-?\\+? ?0?(\\*|\\d*)\\.?(\\d*|\\*)([diouxXfFeEgGaAcsbq])(.*)"
        --            \____ _____/\___ ____/   \____ ____/\_________ _________/ \ /
        --                 V          V             V               V            V
        --               flags    field width  precision   format character     rest
        -- field width and precision can be specified with a '*' instead of a digit,
        -- in which case printf will accept one more argument for each '*' used


prop_checkUuoeCmd1 :: Bool
prop_checkUuoeCmd1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd "echo $(date)"
prop_checkUuoeCmd2 :: Bool
prop_checkUuoeCmd2 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd "echo `date`"
prop_checkUuoeCmd3 :: Bool
prop_checkUuoeCmd3 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd "echo \"$(date)\""
prop_checkUuoeCmd4 :: Bool
prop_checkUuoeCmd4 = CommandCheck -> String -> Bool
verify CommandCheck
checkUuoeCmd "echo \"`date`\""
prop_checkUuoeCmd5 :: Bool
prop_checkUuoeCmd5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUuoeCmd "echo \"The time is $(date)\""
prop_checkUuoeCmd6 :: Bool
prop_checkUuoeCmd6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUuoeCmd "echo \"$(<file)\""
checkUuoeCmd :: CommandCheck
checkUuoeCmd = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "echo") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments) where
    msg :: Id -> m ()
msg id :: Id
id = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id 2005 "Useless echo? Instead of 'echo $(cmd)', just use 'cmd'."
    f :: [Token] -> f ()
f [token :: Token
token] = Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
tokenIsJustCommandOutput Token
token) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ Id -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
msg (Token -> Id
getId Token
token)
    f _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSetAssignment1 :: Bool
prop_checkSetAssignment1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSetAssignment "set foo 42"
prop_checkSetAssignment2 :: Bool
prop_checkSetAssignment2 = CommandCheck -> String -> Bool
verify CommandCheck
checkSetAssignment "set foo = 42"
prop_checkSetAssignment3 :: Bool
prop_checkSetAssignment3 = CommandCheck -> String -> Bool
verify CommandCheck
checkSetAssignment "set foo=42"
prop_checkSetAssignment4 :: Bool
prop_checkSetAssignment4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSetAssignment "set -- if=/dev/null"
prop_checkSetAssignment5 :: Bool
prop_checkSetAssignment5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSetAssignment "set 'a=5'"
prop_checkSetAssignment6 :: Bool
prop_checkSetAssignment6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSetAssignment "set"
checkSetAssignment :: CommandCheck
checkSetAssignment = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "set") ([Token] -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> m ()
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> f ()
f (var :: Token
var:value :: Token
value:rest :: [Token]
rest) =
        let str :: String
str = Token -> String
literal Token
var in
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isVariableName String
str Bool -> Bool -> Bool
|| String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
isAssignment String
str) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
msg (Token -> Id
getId Token
var)
    f (var :: Token
var:_) =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
isAssignment (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> String
literal Token
var) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
msg (Token -> Id
getId Token
var)
    f _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    msg :: Id -> m ()
msg id :: Id
id = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id 2121 "To assign a variable, use just 'var=value', no 'set ..'."

    isAssignment :: t Char -> Bool
isAssignment str :: t Char
str = '=' Char -> t Char -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t Char
str
    literal :: Token -> String
literal (T_NormalWord _ l :: [Token]
l) = (Token -> String) -> [Token] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> String
literal [Token]
l
    literal (T_Literal _ str :: String
str) = String
str
    literal _ = "*"


prop_checkExportedExpansions1 :: Bool
prop_checkExportedExpansions1 = CommandCheck -> String -> Bool
verify CommandCheck
checkExportedExpansions "export $foo"
prop_checkExportedExpansions2 :: Bool
prop_checkExportedExpansions2 = CommandCheck -> String -> Bool
verify CommandCheck
checkExportedExpansions "export \"$foo\""
prop_checkExportedExpansions3 :: Bool
prop_checkExportedExpansions3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExportedExpansions "export foo"
prop_checkExportedExpansions4 :: Bool
prop_checkExportedExpansions4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkExportedExpansions "export ${foo?}"
checkExportedExpansions :: CommandCheck
checkExportedExpansions = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "export") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: Token -> m ()
check t :: Token
t = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Token
var <- Token -> Maybe Token
getSingleUnmodifiedVariable Token
t
        let name :: String
name = Token -> String
bracedString Token
var
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> (String -> m ()) -> String -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) 2163 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            "This does not export '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ "'. Remove $/${} for that, or use ${var?} to quiet."

prop_checkReadExpansions1 :: Bool
prop_checkReadExpansions1 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions "read $var"
prop_checkReadExpansions2 :: Bool
prop_checkReadExpansions2 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions "read -r $var"
prop_checkReadExpansions3 :: Bool
prop_checkReadExpansions3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions "read -p $var"
prop_checkReadExpansions4 :: Bool
prop_checkReadExpansions4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions "read -rd $delim name"
prop_checkReadExpansions5 :: Bool
prop_checkReadExpansions5 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions "read \"$var\""
prop_checkReadExpansions6 :: Bool
prop_checkReadExpansions6 = CommandCheck -> String -> Bool
verify CommandCheck
checkReadExpansions "read -a $var"
prop_checkReadExpansions7 :: Bool
prop_checkReadExpansions7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions "read $1"
prop_checkReadExpansions8 :: Bool
prop_checkReadExpansions8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkReadExpansions "read ${var?}"
checkReadExpansions :: CommandCheck
checkReadExpansions = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "read") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check
  where
    options :: Token -> Maybe [(String, Token)]
options = String -> Token -> Maybe [(String, Token)]
getGnuOpts "sreu:n:N:i:p:a:"
    getVars :: Token -> [Token]
getVars cmd :: Token
cmd = [Token] -> Maybe [Token] -> [Token]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [Token] -> [Token]) -> Maybe [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ do
        [(String, Token)]
opts <- Token -> Maybe [(String, Token)]
options Token
cmd
        [Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return ([Token] -> Maybe [Token])
-> ([(String, Token)] -> [Token])
-> [(String, Token)]
-> Maybe [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((String, Token) -> Token) -> [(String, Token)] -> [Token]
forall a b. (a -> b) -> [a] -> [b]
map (String, Token) -> Token
forall a b. (a, b) -> b
snd ([(String, Token)] -> Maybe [Token])
-> [(String, Token)] -> Maybe [Token]
forall a b. (a -> b) -> a -> b
$ ((String, Token) -> Bool) -> [(String, Token)] -> [(String, Token)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(x :: String
x,_) -> String
x String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "" Bool -> Bool -> Bool
|| String
x String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "a") [(String, Token)]
opts

    check :: Token -> m ()
check cmd :: Token
cmd = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warning ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getVars Token
cmd
    warning :: Token -> m ()
warning t :: Token
t = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Token
var <- Token -> Maybe Token
getSingleUnmodifiedVariable Token
t
        let name :: String
name = Token -> String
bracedString Token
var
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
name   -- e.g. not $1
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> (String -> m ()) -> String -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) 2229 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            "This does not read '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ "'. Remove $/${} for that, or use ${var?} to quiet."

-- Return the single variable expansion that makes up this word, if any.
-- e.g. $foo -> $foo, "$foo"'' -> $foo , "hello $name" -> Nothing
getSingleUnmodifiedVariable :: Token -> Maybe Token
getSingleUnmodifiedVariable :: Token -> Maybe Token
getSingleUnmodifiedVariable word :: Token
word =
    case Token -> [Token]
getWordParts Token
word of
        [t :: Token
t@(T_DollarBraced {})] ->
            let contents :: String
contents = Token -> String
bracedString Token
t
                name :: String
name = String -> String
getBracedReference String
contents
            in Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (String
contents String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
name) Maybe () -> Maybe Token -> Maybe Token
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
t
        _ -> Maybe Token
forall a. Maybe a
Nothing

prop_checkAliasesUsesArgs1 :: Bool
prop_checkAliasesUsesArgs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkAliasesUsesArgs "alias a='cp $1 /a'"
prop_checkAliasesUsesArgs2 :: Bool
prop_checkAliasesUsesArgs2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkAliasesUsesArgs "alias $1='foo'"
prop_checkAliasesUsesArgs3 :: Bool
prop_checkAliasesUsesArgs3 = CommandCheck -> String -> Bool
verify CommandCheck
checkAliasesUsesArgs "alias a=\"echo \\${@}\""
checkAliasesUsesArgs :: CommandCheck
checkAliasesUsesArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "alias") ([Token] -> Analysis
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    re :: Regex
re = String -> Regex
mkRegex "\\$\\{?[0-9*@]"
    f :: [Token] -> Analysis
f = (Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArg
    checkArg :: Token -> f ()
checkArg arg :: Token
arg =
        let string :: String
string = Maybe String -> String
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt (Maybe String -> Token -> Maybe String
forall a b. a -> b -> a
const (Maybe String -> Token -> Maybe String)
-> Maybe String -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return "_") Token
arg in
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ('=' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
string Bool -> Bool -> Bool
&& String
string String -> Regex -> Bool
`matches` Regex
re) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
arg) 2142
                    "Aliases can't use positional parameters. Use a function."


prop_checkAliasesExpandEarly1 :: Bool
prop_checkAliasesExpandEarly1 = CommandCheck -> String -> Bool
verify CommandCheck
checkAliasesExpandEarly "alias foo=\"echo $PWD\""
prop_checkAliasesExpandEarly2 :: Bool
prop_checkAliasesExpandEarly2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkAliasesExpandEarly "alias -p"
prop_checkAliasesExpandEarly3 :: Bool
prop_checkAliasesExpandEarly3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkAliasesExpandEarly "alias foo='echo {1..10}'"
checkAliasesExpandEarly :: CommandCheck
checkAliasesExpandEarly = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "alias") ([Token] -> Analysis
f ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    f :: [Token] -> Analysis
f = (Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArg
    checkArg :: Token -> m ()
checkArg arg :: Token
arg | '=' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
arg) =
        [Token] -> (Token -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take 1 ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ (Token -> Bool) -> [Token] -> [Token]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isLiteral) ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
arg) ((Token -> m ()) -> m ()) -> (Token -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$
            \x :: Token
x -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
x) 2139 "This expands when defined, not when used. Consider escaping."
    checkArg _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnsetGlobs1 :: Bool
prop_checkUnsetGlobs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkUnsetGlobs "unset foo[1]"
prop_checkUnsetGlobs2 :: Bool
prop_checkUnsetGlobs2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkUnsetGlobs "unset foo"
checkUnsetGlobs :: CommandCheck
checkUnsetGlobs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "unset") ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
arguments)
  where
    check :: Token -> f ()
check arg :: Token
arg =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
arg) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
arg) 2184 "Quote arguments to unset so they're not glob expanded."


prop_checkFindWithoutPath1 :: Bool
prop_checkFindWithoutPath1 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindWithoutPath "find -type f"
prop_checkFindWithoutPath2 :: Bool
prop_checkFindWithoutPath2 = CommandCheck -> String -> Bool
verify CommandCheck
checkFindWithoutPath "find"
prop_checkFindWithoutPath3 :: Bool
prop_checkFindWithoutPath3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath "find . -type f"
prop_checkFindWithoutPath4 :: Bool
prop_checkFindWithoutPath4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath "find -H -L \"$path\" -print"
prop_checkFindWithoutPath5 :: Bool
prop_checkFindWithoutPath5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath "find -O3 ."
prop_checkFindWithoutPath6 :: Bool
prop_checkFindWithoutPath6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath "find -D exec ."
prop_checkFindWithoutPath7 :: Bool
prop_checkFindWithoutPath7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath "find --help"
prop_checkFindWithoutPath8 :: Bool
prop_checkFindWithoutPath8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindWithoutPath "find -Hx . -print"
checkFindWithoutPath :: CommandCheck
checkFindWithoutPath = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "find") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> f ()
f t :: Token
t@(T_SimpleCommand _ _ (cmd :: Token
cmd:args :: [Token]
args)) =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token
t Token -> String -> Bool
`hasFlag` "help" Bool -> Bool -> Bool
|| [Token] -> Bool
hasPath [Token]
args) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
cmd) 2185 "Some finds don't have a default path. Specify '.' explicitly."

    -- This is a bit of a kludge. find supports flag arguments both before and
    -- after the path, as well as multiple non-flag arguments that are not the
    -- path. We assume that all the pre-path flags are single characters from a
    -- list of GNU and macOS flags.
    hasPath :: [Token] -> Bool
hasPath (first :: Token
first:rest :: [Token]
rest) =
        let flag :: String
flag = Maybe String -> String
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt (Maybe String -> Token -> Maybe String
forall a b. a -> b -> a
const (Maybe String -> Token -> Maybe String)
-> Maybe String -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return "___") Token
first in
            Bool -> Bool
not ("-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
flag) Bool -> Bool -> Bool
|| String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
isLeadingFlag String
flag Bool -> Bool -> Bool
&& [Token] -> Bool
hasPath [Token]
rest
    hasPath [] = Bool
False
    isLeadingFlag :: t Char -> Bool
isLeadingFlag flag :: t Char
flag = t Char -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length t Char
flag Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= 2 Bool -> Bool -> Bool
|| (Char -> Bool) -> t Char -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
leadingFlagChars) t Char
flag
    leadingFlagChars :: String
leadingFlagChars="-EHLPXdfsxO0123456789"


prop_checkTimeParameters1 :: Bool
prop_checkTimeParameters1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTimeParameters "time -f lol sleep 10"
prop_checkTimeParameters2 :: Bool
prop_checkTimeParameters2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimeParameters "time sleep 10"
prop_checkTimeParameters3 :: Bool
prop_checkTimeParameters3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimeParameters "time -p foo"
prop_checkTimeParameters4 :: Bool
prop_checkTimeParameters4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimeParameters "command time -f lol sleep 10"
checkTimeParameters :: CommandCheck
checkTimeParameters = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "time") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f (T_SimpleCommand _ _ (cmd :: Token
cmd:args :: Token
args:_)) =
        [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash, Shell
Sh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            let s :: String
s = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
args in
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ("-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s Bool -> Bool -> Bool
&& String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "-p") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
cmd) 2023 "The shell may override 'time' as seen in man time(1). Use 'command time ..' for that one."

    f _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkTimedCommand1 :: Bool
prop_checkTimedCommand1 = CommandCheck -> String -> Bool
verify CommandCheck
checkTimedCommand "#!/bin/sh\ntime -p foo | bar"
prop_checkTimedCommand2 :: Bool
prop_checkTimedCommand2 = CommandCheck -> String -> Bool
verify CommandCheck
checkTimedCommand "#!/bin/dash\ntime ( foo; bar; )"
prop_checkTimedCommand3 :: Bool
prop_checkTimedCommand3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkTimedCommand "#!/bin/sh\ntime sleep 1"
checkTimedCommand :: CommandCheck
checkTimedCommand = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "time") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f where
    f :: Token -> m ()
f (T_SimpleCommand _ _ (c :: Token
c:args :: [Token]
args@(_:_))) =
        [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Dash] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
            let cmd :: Token
cmd = [Token] -> Token
forall a. [a] -> a
last [Token]
args -- "time" is parsed with a command as argument
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isPiped Token
cmd) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
c) 2176 "'time' is undefined for pipelines. time single stage or bash -c instead."
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Maybe Bool
forall (m :: * -> *). MonadFail m => Token -> m Bool
isSimple Token
cmd Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
cmd) 2177 "'time' is undefined for compound commands, time sh -c instead."
    f _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isPiped :: Token -> Bool
isPiped cmd :: Token
cmd =
        case Token
cmd of
            T_Pipeline _ _ (_:_:_) -> Bool
True
            _ -> Bool
False
    getCommand :: Token -> m Token
getCommand cmd :: Token
cmd =
        case Token
cmd of
            T_Pipeline _ _ (T_Redirecting _ _ a :: Token
a : _) -> Token -> m Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
a
            _ -> String -> m Token
forall (m :: * -> *) a. MonadFail m => String -> m a
fail ""
    isSimple :: Token -> m Bool
isSimple cmd :: Token
cmd = do
        Token
innerCommand <- Token -> m Token
forall (m :: * -> *). MonadFail m => Token -> m Token
getCommand Token
cmd
        case Token
innerCommand of
            T_SimpleCommand {} -> Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            _ -> Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False

prop_checkLocalScope1 :: Bool
prop_checkLocalScope1 = CommandCheck -> String -> Bool
verify CommandCheck
checkLocalScope "local foo=3"
prop_checkLocalScope2 :: Bool
prop_checkLocalScope2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkLocalScope "f() { local foo=3; }"
checkLocalScope :: CommandCheck
checkLocalScope = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "local") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ \t :: Token
t ->
    [Shell] -> Analysis -> Analysis
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash, Shell
Dash] (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do -- Ksh allows it, Sh doesn't support local
        [Token]
path <- Token -> RWST Parameters [TokenComment] Cache Identity [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
        Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isFunctionLike [Token]
path) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) 2168 "'local' is only valid in functions."

prop_checkDeprecatedTempfile1 :: Bool
prop_checkDeprecatedTempfile1 = CommandCheck -> String -> Bool
verify CommandCheck
checkDeprecatedTempfile "var=$(tempfile)"
prop_checkDeprecatedTempfile2 :: Bool
prop_checkDeprecatedTempfile2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkDeprecatedTempfile "tempfile=$(mktemp)"
checkDeprecatedTempfile :: CommandCheck
checkDeprecatedTempfile = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "tempfile") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
    \t :: Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) 2186 "tempfile is deprecated. Use mktemp instead."

prop_checkDeprecatedEgrep :: Bool
prop_checkDeprecatedEgrep = CommandCheck -> String -> Bool
verify CommandCheck
checkDeprecatedEgrep "egrep '.+'"
checkDeprecatedEgrep :: CommandCheck
checkDeprecatedEgrep = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "egrep") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
    \t :: Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) 2196 "egrep is non-standard and deprecated. Use grep -E instead."

prop_checkDeprecatedFgrep :: Bool
prop_checkDeprecatedFgrep = CommandCheck -> String -> Bool
verify CommandCheck
checkDeprecatedFgrep "fgrep '*' files"
checkDeprecatedFgrep :: CommandCheck
checkDeprecatedFgrep = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "fgrep") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
    \t :: Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) 2197 "fgrep is non-standard and deprecated. Use grep -F instead."

prop_checkWhileGetoptsCase1 :: Bool
prop_checkWhileGetoptsCase1 = CommandCheck -> String -> Bool
verify CommandCheck
checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; esac; done"
prop_checkWhileGetoptsCase2 :: Bool
prop_checkWhileGetoptsCase2 = CommandCheck -> String -> Bool
verify CommandCheck
checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; b) bar;; esac; done"
prop_checkWhileGetoptsCase3 :: Bool
prop_checkWhileGetoptsCase3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase "while getopts 'a:b' x; do case $x in a) foo;; b) bar;; *) :;esac; done"
prop_checkWhileGetoptsCase4 :: Bool
prop_checkWhileGetoptsCase4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase "while getopts 'a:123' x; do case $x in a) foo;; [0-9]) bar;; esac; done"
prop_checkWhileGetoptsCase5 :: Bool
prop_checkWhileGetoptsCase5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkWhileGetoptsCase "while getopts 'a:' x; do case $x in a) foo;; \\?) bar;; *) baz;; esac; done"
checkWhileGetoptsCase :: CommandCheck
checkWhileGetoptsCase = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "getopts") Token -> Analysis
f
  where
    f :: Token -> Analysis
    f :: Token -> Analysis
f t :: Token
t@(T_SimpleCommand _ _ (cmd :: Token
cmd:arg1 :: Token
arg1:_))  = do
        [Token]
path <- Token -> RWST Parameters [TokenComment] Cache Identity [Token]
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m [Token]
getPathM Token
t
        Maybe Analysis -> Analysis
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe Analysis -> Analysis) -> Maybe Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
            String
options <- Token -> Maybe String
getLiteralString Token
arg1
            (T_WhileExpression _ _ body :: [Token]
body) <- (Token -> Maybe Bool) -> [Token] -> Maybe Token
forall a. (a -> Maybe Bool) -> [a] -> Maybe a
findFirst Token -> Maybe Bool
whileLoop [Token]
path
            Token
caseCmd <- (Token -> Maybe Token) -> [Token] -> [Token]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Token
findCase [Token]
body [Token] -> Int -> Maybe Token
forall a. [a] -> Int -> Maybe a
!!! 0
            Analysis -> Maybe Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return (Analysis -> Maybe Analysis) -> Analysis -> Maybe Analysis
forall a b. (a -> b) -> a -> b
$ Id -> [String] -> Token -> Analysis
check (Token -> Id
getId Token
arg1) ((Char -> String) -> String -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Char -> String -> String
forall a. a -> [a] -> [a]
:[]) (String -> [String]) -> String -> [String]
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= ':') String
options) Token
caseCmd
    f _ = () -> Analysis
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check :: Id -> [String] -> Token -> Analysis
    check :: Id -> [String] -> Token -> Analysis
check optId :: Id
optId opts :: [String]
opts (T_CaseExpression id :: Id
id _ list :: [(CaseType, [Token], [Token])]
list) = do
            Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Maybe String
forall a. Maybe a
Nothing Maybe String -> Map (Maybe String) Token -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`Map.member` Map (Maybe String) Token
handledMap) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$ do
                (String -> Analysis) -> [String] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Id -> Id -> String -> Analysis
forall (m :: * -> *) p.
MonadWriter [TokenComment] m =>
p -> Id -> String -> m ()
warnUnhandled Id
optId Id
id) ([String] -> Analysis) -> [String] -> Analysis
forall a b. (a -> b) -> a -> b
$ [Maybe String] -> [String]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe String] -> [String]) -> [Maybe String] -> [String]
forall a b. (a -> b) -> a -> b
$ Map (Maybe String) () -> [Maybe String]
forall k a. Map k a -> [k]
Map.keys Map (Maybe String) ()
notHandled

                Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((Maybe String -> Bool) -> [Maybe String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Maybe String -> Map (Maybe String) Token -> Bool
forall k a. Ord k => k -> Map k a -> Bool
`Map.member` Map (Maybe String) Token
handledMap) [String -> Maybe String
forall a. a -> Maybe a
Just "*",String -> Maybe String
forall a. a -> Maybe a
Just "?"]) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$
                    Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id 2220 "Invalid flags are not handled. Add a *) case."

            ((Maybe String, Token) -> Analysis)
-> [(Maybe String, Token)] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Maybe String, Token) -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
(Maybe String, Token) -> m ()
warnRedundant ([(Maybe String, Token)] -> Analysis)
-> [(Maybe String, Token)] -> Analysis
forall a b. (a -> b) -> a -> b
$ Map (Maybe String) Token -> [(Maybe String, Token)]
forall k a. Map k a -> [(k, a)]
Map.toList Map (Maybe String) Token
notRequested

        where
            handledMap :: Map (Maybe String) Token
handledMap = [(Maybe String, Token)] -> Map (Maybe String) Token
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList (((CaseType, [Token], [Token]) -> [(Maybe String, Token)])
-> [(CaseType, [Token], [Token])] -> [(Maybe String, Token)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (CaseType, [Token], [Token]) -> [(Maybe String, Token)]
forall a c. (a, [Token], c) -> [(Maybe String, Token)]
getHandledStrings [(CaseType, [Token], [Token])]
list)
            requestedMap :: Map (Maybe String) ()
requestedMap = [(Maybe String, ())] -> Map (Maybe String) ()
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(Maybe String, ())] -> Map (Maybe String) ())
-> [(Maybe String, ())] -> Map (Maybe String) ()
forall a b. (a -> b) -> a -> b
$ (String -> (Maybe String, ())) -> [String] -> [(Maybe String, ())]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: String
x -> (String -> Maybe String
forall a. a -> Maybe a
Just String
x, ())) [String]
opts

            notHandled :: Map (Maybe String) ()
notHandled = Map (Maybe String) ()
-> Map (Maybe String) Token -> Map (Maybe String) ()
forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference Map (Maybe String) ()
requestedMap Map (Maybe String) Token
handledMap
            notRequested :: Map (Maybe String) Token
notRequested = Map (Maybe String) Token
-> Map (Maybe String) () -> Map (Maybe String) Token
forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference Map (Maybe String) Token
handledMap Map (Maybe String) ()
requestedMap

    warnUnhandled :: p -> Id -> String -> m ()
warnUnhandled optId :: p
optId caseId :: Id
caseId str :: String
str =
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
caseId 2213 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "getopts specified -" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ ", but it's not handled by this 'case'."

    warnRedundant :: (Maybe String, Token) -> m ()
warnRedundant (key :: Maybe String
key, expr :: Token
expr) = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
str <- Maybe String
key
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` ["*", ":", "?"]
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
expr) 2214 "This case is not specified by getopts."

    getHandledStrings :: (a, [Token], c) -> [(Maybe String, Token)]
getHandledStrings (_, globs :: [Token]
globs, _) =
        (Token -> (Maybe String, Token))
-> [Token] -> [(Maybe String, Token)]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: Token
x -> (Token -> Maybe String
literal Token
x, Token
x)) [Token]
globs

    literal :: Token -> Maybe String
    literal :: Token -> Maybe String
literal t :: Token
t = do
        Token -> Maybe String
getLiteralString Token
t Maybe String -> Maybe String -> Maybe String
forall a. Semigroup a => a -> a -> a
<> Token -> Maybe String
fromGlob Token
t

    fromGlob :: Token -> Maybe String
fromGlob t :: Token
t =
        case Token
t of
            T_Glob _ ('[':c :: Char
c:']':[]) -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return [Char
c]
            T_Glob _ "*" -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return "*"
            T_Glob _ "?" -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return "?"
            _ -> Maybe String
forall a. Maybe a
Nothing

    whileLoop :: Token -> Maybe Bool
whileLoop t :: Token
t =
        case Token
t of
            T_WhileExpression {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Script {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            _ -> Maybe Bool
forall a. Maybe a
Nothing

    findCase :: Token -> Maybe Token
findCase t :: Token
t =
        case Token
t of
            T_Annotation _ _ x :: Token
x -> Token -> Maybe Token
findCase Token
x
            T_Pipeline _ _ [x :: Token
x] -> Token -> Maybe Token
findCase Token
x
            T_Redirecting _ _ x :: Token
x@(T_CaseExpression {}) -> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
x
            _ -> Maybe Token
forall a. Maybe a
Nothing

prop_checkCatastrophicRm1 :: Bool
prop_checkCatastrophicRm1 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -r $1/$2"
prop_checkCatastrophicRm2 :: Bool
prop_checkCatastrophicRm2 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -r /home/$foo"
prop_checkCatastrophicRm3 :: Bool
prop_checkCatastrophicRm3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm "rm -r /home/${USER:?}/*"
prop_checkCatastrophicRm4 :: Bool
prop_checkCatastrophicRm4 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -fr /home/$(whoami)/*"
prop_checkCatastrophicRm5 :: Bool
prop_checkCatastrophicRm5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm "rm -r /home/${USER:-thing}/*"
prop_checkCatastrophicRm6 :: Bool
prop_checkCatastrophicRm6 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm --recursive /etc/*$config*"
prop_checkCatastrophicRm8 :: Bool
prop_checkCatastrophicRm8 = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -rf /home"
prop_checkCatastrophicRm10 :: Bool
prop_checkCatastrophicRm10= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm "rm -r \"${DIR}\"/{.gitignore,.gitattributes,ci}"
prop_checkCatastrophicRm11 :: Bool
prop_checkCatastrophicRm11= CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -r /{bin,sbin}/$exec"
prop_checkCatastrophicRm12 :: Bool
prop_checkCatastrophicRm12= CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -r /{{usr,},{bin,sbin}}/$exec"
prop_checkCatastrophicRm13 :: Bool
prop_checkCatastrophicRm13= CommandCheck -> String -> Bool
verifyNot CommandCheck
checkCatastrophicRm "rm -r /{{a,b},{c,d}}/$exec"
prop_checkCatastrophicRmA :: Bool
prop_checkCatastrophicRmA = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -rf /usr /lib/nvidia-current/xorg/xorg"
prop_checkCatastrophicRmB :: Bool
prop_checkCatastrophicRmB = CommandCheck -> String -> Bool
verify CommandCheck
checkCatastrophicRm "rm -rf \"$STEAMROOT/\"*"
checkCatastrophicRm :: CommandCheck
checkCatastrophicRm = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "rm") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ \t :: Token
t ->
    Bool -> Analysis -> Analysis
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isRecursive Token
t) (Analysis -> Analysis) -> Analysis -> Analysis
forall a b. (a -> b) -> a -> b
$
        (Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ((Token -> Analysis) -> [Token] -> Analysis
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkWord ([Token] -> Analysis) -> (Token -> [Token]) -> Token -> Analysis
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [Token]
braceExpand) ([Token] -> Analysis) -> [Token] -> Analysis
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
  where
    isRecursive :: Token -> Bool
isRecursive = (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["r", "R", "recursive"]) ([String] -> Bool) -> (Token -> [String]) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String])
-> (Token -> [(Token, String)]) -> Token -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags

    checkWord :: Token -> f ()
checkWord token :: Token
token =
        case Token -> Maybe String
getLiteralString Token
token of
            Just str :: String
str ->
                Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> String
fixPath String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
importantPaths) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Code -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) 2114 "Warning: deletes a system directory."
            Nothing ->
                Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkWord' Token
token

    checkWord' :: Token -> m ()
checkWord' token :: Token
token = m () -> Maybe (m ()) -> m ()
forall a. a -> Maybe a -> a
fromMaybe (() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
filename <- Token -> Maybe String
getPotentialPath Token
token
        let path :: String
path = String -> String
fixPath String
filename
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> (m () -> m ()) -> m () -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
path String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
importantPaths) (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) 2115 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Use \"${var:?}\" to ensure this never expands to " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
path String -> String -> String
forall a. [a] -> [a] -> [a]
++ " ."

    fixPath :: String -> String
fixPath filename :: String
filename =
        let normalized :: String
normalized = Char -> String -> String
forall a. Eq a => a -> [a] -> [a]
skipRepeating '/' (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> String -> String
forall a. Eq a => a -> [a] -> [a]
skipRepeating '*' (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String
filename in
            if String
normalized String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "/" then String
normalized else Char -> String -> String
forall a. Eq a => a -> [a] -> [a]
stripTrailing '/' String
normalized

    getPotentialPath :: Token -> Maybe String
getPotentialPath = (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt Token -> Maybe String
f
      where
        f :: Token -> Maybe String
f (T_Glob _ str :: String
str) = String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
str
        f (T_DollarBraced _ _ word :: Token
word) =
            let var :: String
var = Token -> String
onlyLiteralString Token
word in
                -- This shouldn't handle non-colon cases.
                if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
var) [":?", ":-", ":="]
                then Maybe String
forall a. Maybe a
Nothing
                else String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return ""
        f _ = String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return ""

    stripTrailing :: a -> [a] -> [a]
stripTrailing c :: a
c = [a] -> [a]
forall a. [a] -> [a]
reverse ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> Bool) -> [a] -> [a]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c) ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [a] -> [a]
forall a. [a] -> [a]
reverse
    skipRepeating :: a -> [a] -> [a]
skipRepeating c :: a
c (a :: a
a:b :: a
b:rest :: [a]
rest) | a
a a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
b Bool -> Bool -> Bool
&& a
b a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c = a -> [a] -> [a]
skipRepeating a
c (a
ba -> [a] -> [a]
forall a. a -> [a] -> [a]
:[a]
rest)
    skipRepeating c :: a
c (a :: a
a:r :: [a]
r) = a
aa -> [a] -> [a]
forall a. a -> [a] -> [a]
:a -> [a] -> [a]
skipRepeating a
c [a]
r
    skipRepeating _ [] = []

    paths :: [String]
paths = [
        "", "/bin", "/etc", "/home", "/mnt", "/usr", "/usr/share", "/usr/local",
        "/var", "/lib", "/dev", "/media", "/boot", "/lib64", "/usr/bin"
        ]
    importantPaths :: [String]
importantPaths = (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null) ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$
        ["", "/", "/*", "/*/*"] [String] -> (String -> [String]) -> [String]
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (\x :: String
x -> (String -> String) -> [String] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (String -> String -> String
forall a. [a] -> [a] -> [a]
++String
x) [String]
paths)


prop_checkLetUsage1 :: Bool
prop_checkLetUsage1 = CommandCheck -> String -> Bool
verify CommandCheck
checkLetUsage "let a=1"
prop_checkLetUsage2 :: Bool
prop_checkLetUsage2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkLetUsage "(( a=1 ))"
checkLetUsage :: CommandCheck
checkLetUsage = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly "let") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Bash,Shell
Ksh] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) 2219 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Instead of 'let expr', prefer (( expr )) ."


missingDestination :: (Token -> f ()) -> Token -> f ()
missingDestination handler :: Token -> f ()
handler token :: Token
token = do
    case [Token]
params of
        [single :: Token
single] -> do
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Bool
hasTarget Bool -> Bool -> Bool
|| Token -> Bool
mayBecomeMultipleArgs Token
single) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Token -> f ()
handler Token
token
        _ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    args :: [(Token, String)]
args = Token -> [(Token, String)]
getAllFlags Token
token
    params :: [Token]
params = ((Token, String) -> Token) -> [(Token, String)] -> [Token]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> Token
forall a b. (a, b) -> a
fst ([(Token, String)] -> [Token]) -> [(Token, String)] -> [Token]
forall a b. (a -> b) -> a -> b
$ ((Token, String) -> Bool) -> [(Token, String)] -> [(Token, String)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(_,x :: String
x) -> String
x String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "") [(Token, String)]
args
    hasTarget :: Bool
hasTarget =
        (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\x :: String
x -> String
x String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "" Bool -> Bool -> Bool
&& String
x String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` "target-directory") ([String] -> Bool) -> [String] -> Bool
forall a b. (a -> b) -> a -> b
$
            ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd [(Token, String)]
args

prop_checkMvArguments1 :: Bool
prop_checkMvArguments1 = CommandCheck -> String -> Bool
verify    CommandCheck
checkMvArguments "mv 'foo bar'"
prop_checkMvArguments2 :: Bool
prop_checkMvArguments2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv foo bar"
prop_checkMvArguments3 :: Bool
prop_checkMvArguments3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv 'foo bar'{,bak}"
prop_checkMvArguments4 :: Bool
prop_checkMvArguments4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv \"$@\""
prop_checkMvArguments5 :: Bool
prop_checkMvArguments5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv -t foo bar"
prop_checkMvArguments6 :: Bool
prop_checkMvArguments6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv --target-directory=foo bar"
prop_checkMvArguments7 :: Bool
prop_checkMvArguments7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv --target-direc=foo bar"
prop_checkMvArguments8 :: Bool
prop_checkMvArguments8 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv --version"
prop_checkMvArguments9 :: Bool
prop_checkMvArguments9 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkMvArguments "mv \"${!var}\""
checkMvArguments :: CommandCheck
checkMvArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "mv") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ (Token -> Analysis) -> Token -> Analysis
forall (f :: * -> *). Monad f => (Token -> f ()) -> Token -> f ()
missingDestination Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) 2224 "This mv has no destination. Check the arguments."

checkCpArguments :: CommandCheck
checkCpArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "cp") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ (Token -> Analysis) -> Token -> Analysis
forall (f :: * -> *). Monad f => (Token -> f ()) -> Token -> f ()
missingDestination Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) 2225 "This cp has no destination. Check the arguments."

checkLnArguments :: CommandCheck
checkLnArguments = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "ln") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$ (Token -> Analysis) -> Token -> Analysis
forall (f :: * -> *). Monad f => (Token -> f ()) -> Token -> f ()
missingDestination Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) 2226 "This ln has no destination. Check the arguments, or specify '.' explicitly."


prop_checkFindRedirections1 :: Bool
prop_checkFindRedirections1 = CommandCheck -> String -> Bool
verify    CommandCheck
checkFindRedirections "find . -exec echo {} > file \\;"
prop_checkFindRedirections2 :: Bool
prop_checkFindRedirections2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindRedirections "find . -exec echo {} \\; > file"
prop_checkFindRedirections3 :: Bool
prop_checkFindRedirections3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkFindRedirections "find . -execdir sh -c 'foo > file' \\;"
checkFindRedirections :: CommandCheck
checkFindRedirections = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "find") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = do
        Maybe Token
redirecting <- Token -> m (Maybe Token)
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
        case Maybe Token
redirecting of
            Just (T_Redirecting _ redirs :: [Token]
redirs@(_:_) (T_SimpleCommand _ _ args :: [Token]
args@(_:_:_))) -> do
                -- This assumes IDs are sequential, which is mostly but not always true.
                let minRedir :: Id
minRedir = [Id] -> Id
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum ([Id] -> Id) -> [Id] -> Id
forall a b. (a -> b) -> a -> b
$ (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
redirs
                let maxArg :: Id
maxArg   = [Id] -> Id
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum ([Id] -> Id) -> [Id] -> Id
forall a b. (a -> b) -> a -> b
$ (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
args
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Id
minRedir Id -> Id -> Bool
forall a. Ord a => a -> a -> Bool
< Id
maxArg) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
minRedir 2227
                        "Redirection applies to the find command itself. Rewrite to work per action (or move to end)."
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkWhich :: Bool
prop_checkWhich = CommandCheck -> String -> Bool
verify CommandCheck
checkWhich "which '.+'"
checkWhich :: CommandCheck
checkWhich = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "which") ((Token -> Analysis) -> CommandCheck)
-> (Token -> Analysis) -> CommandCheck
forall a b. (a -> b) -> a -> b
$
    \t :: Token
t -> Id -> Code -> String -> Analysis
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) 2230 "which is non-standard. Use builtin 'command -v' instead."

prop_checkSudoRedirect1 :: Bool
prop_checkSudoRedirect1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect "sudo echo 3 > /proc/file"
prop_checkSudoRedirect2 :: Bool
prop_checkSudoRedirect2 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect "sudo cmd < input"
prop_checkSudoRedirect3 :: Bool
prop_checkSudoRedirect3 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect "sudo cmd >> file"
prop_checkSudoRedirect4 :: Bool
prop_checkSudoRedirect4 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoRedirect "sudo cmd &> file"
prop_checkSudoRedirect5 :: Bool
prop_checkSudoRedirect5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoRedirect "sudo cmd 2>&1"
prop_checkSudoRedirect6 :: Bool
prop_checkSudoRedirect6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoRedirect "sudo cmd 2> log"
prop_checkSudoRedirect7 :: Bool
prop_checkSudoRedirect7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoRedirect "sudo cmd > /dev/null 2>&1"
checkSudoRedirect :: CommandCheck
checkSudoRedirect = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "sudo") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = do
        Maybe Token
t_redir <- Token -> m (Maybe Token)
forall (m :: * -> *).
MonadReader Parameters m =>
Token -> m (Maybe Token)
getClosestCommandM Token
t
        case Maybe Token
t_redir of
            Just (T_Redirecting _ redirs :: [Token]
redirs _) ->
                (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnAbout [Token]
redirs
    warnAbout :: Token -> m ()
warnAbout (T_FdRedirect _ s :: String
s (T_IoFile id :: Id
id op :: Token
op file :: Token
file))
        | (String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "" Bool -> Bool -> Bool
|| String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "&") Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
special Token
file) =
        case Token
op of
            T_Less _ ->
              Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
op) 2024
                "sudo doesn't affect redirects. Use sudo cat file | .."
            T_Greater _ ->
              Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
op) 2024
                "sudo doesn't affect redirects. Use ..| sudo tee file"
            T_DGREAT _ ->
              Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
op) 2024
                "sudo doesn't affect redirects. Use .. | sudo tee -a file"
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    warnAbout _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    special :: Token -> Bool
special file :: Token
file = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
file) String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "/dev/null"

prop_checkSudoArgs1 :: Bool
prop_checkSudoArgs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoArgs "sudo cd /root"
prop_checkSudoArgs2 :: Bool
prop_checkSudoArgs2 = CommandCheck -> String -> Bool
verify CommandCheck
checkSudoArgs "sudo export x=3"
prop_checkSudoArgs3 :: Bool
prop_checkSudoArgs3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs "sudo ls /usr/local/protected"
prop_checkSudoArgs4 :: Bool
prop_checkSudoArgs4 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs "sudo ls && export x=3"
prop_checkSudoArgs5 :: Bool
prop_checkSudoArgs5 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs "sudo echo ls"
prop_checkSudoArgs6 :: Bool
prop_checkSudoArgs6 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs "sudo -n -u export ls"
prop_checkSudoArgs7 :: Bool
prop_checkSudoArgs7 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSudoArgs "sudo docker export foo"
checkSudoArgs :: CommandCheck
checkSudoArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "sudo") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        [(String, Token)]
opts <- Token -> Maybe [(String, Token)]
parseOpts Token
t
        let nonFlags :: [Token]
nonFlags = ((String, Token) -> Token) -> [(String, Token)] -> [Token]
forall a b. (a -> b) -> [a] -> [b]
map (String, Token) -> Token
forall a b. (a, b) -> b
snd ([(String, Token)] -> [Token]) -> [(String, Token)] -> [Token]
forall a b. (a -> b) -> a -> b
$ ((String, Token) -> Bool) -> [(String, Token)] -> [(String, Token)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(flag :: String
flag, _) -> String
flag String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "") [(String, Token)]
opts
        Token
commandArg <- [Token]
nonFlags [Token] -> Int -> Maybe Token
forall a. [a] -> Int -> Maybe a
!!! 0
        String
command <- Token -> Maybe String
getLiteralString Token
commandArg
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
command String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
builtins
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) 2232 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Can't use sudo with builtins like " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
command String -> String -> String
forall a. [a] -> [a] -> [a]
++ ". Did you want sudo sh -c .. instead?"
    builtins :: [String]
builtins = [ "cd", "eval", "export", "history", "read", "source", "wait" ]
    -- This mess is why ShellCheck prefers not to know.
    parseOpts :: Token -> Maybe [(String, Token)]
parseOpts = String -> Token -> Maybe [(String, Token)]
getBsdOpts "vAknSbEHPa:g:h:p:u:c:T:r:"

prop_checkSourceArgs1 :: Bool
prop_checkSourceArgs1 = CommandCheck -> String -> Bool
verify CommandCheck
checkSourceArgs "#!/bin/sh\n. script arg"
prop_checkSourceArgs2 :: Bool
prop_checkSourceArgs2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSourceArgs "#!/bin/sh\n. script"
prop_checkSourceArgs3 :: Bool
prop_checkSourceArgs3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkSourceArgs "#!/bin/bash\n. script arg"
checkSourceArgs :: CommandCheck
checkSourceArgs = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Exactly ".") Token -> Analysis
forall (m :: * -> *).
(MonadReader Parameters m, MonadWriter [TokenComment] m) =>
Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = [Shell] -> m () -> m ()
forall (m :: * -> *) (t :: * -> *).
(MonadReader Parameters m, Foldable t) =>
t Shell -> m () -> m ()
whenShell [Shell
Sh, Shell
Dash] (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
        case Token -> [Token]
arguments Token
t of
            (file :: Token
file:arg1 :: Token
arg1:_) -> Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
arg1) 2240 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                "The dot command does not support arguments in sh/dash. Set them as variables."
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkChmodDashr1 :: Bool
prop_checkChmodDashr1 = CommandCheck -> String -> Bool
verify CommandCheck
checkChmodDashr "chmod -r 0755 dir"
prop_checkChmodDashr2 :: Bool
prop_checkChmodDashr2 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkChmodDashr "chmod -R 0755 dir"
prop_checkChmodDashr3 :: Bool
prop_checkChmodDashr3 = CommandCheck -> String -> Bool
verifyNot CommandCheck
checkChmodDashr "chmod a-r dir"
checkChmodDashr :: CommandCheck
checkChmodDashr = CommandName -> (Token -> Analysis) -> CommandCheck
CommandCheck (String -> CommandName
Basename "chmod") Token -> Analysis
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
f
  where
    f :: Token -> m ()
f t :: Token
t = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
    check :: Token -> m ()
check t :: Token
t = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
flag <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
flag String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "-r"
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) 2253 "Use -R to recurse, or explicitly a-r to remove read permissions."

return []
runTests :: IO Bool
runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])