@@ -5053,8 +5053,8 @@ prop_checkCommandIsUnreachable6 = verifyNot checkCommandIsUnreachable "return ||
50535053prop_checkCommandIsUnreachable7 = verifyNot checkCommandIsUnreachable " return 2>/dev/null ||:"
50545054prop_checkCommandIsUnreachable8 = verifyNot checkCommandIsUnreachable " return; echo 'reachable when not in function'"
50555055prop_checkCommandIsUnreachable9 = verify checkCommandIsUnreachable " f() { return; echo unreachable; }"
5056- prop_checkCommandIsUnreachable10 = verifyNot checkCommandIsUnreachable " f; f () { :; }; exit $? "
5057- prop_checkCommandIsUnreachable11 = verifyNot checkCommandIsUnreachable " PS4func; PS4func () { echo test ; }; exit $? "
5056+ prop_checkCommandIsUnreachable10 = verifyNot checkCommandIsUnreachable " PS4func () { :; }; PS4='$(PS4func)'; exit "
5057+ prop_checkCommandIsUnreachable11 = verifyNot checkCommandIsUnreachable " f () { : ; }; var=`f`; exit "
50585058checkCommandIsUnreachable params t =
50595059 case t of
50605060 T_Pipeline {} -> sequence_ $ do
@@ -5068,7 +5068,7 @@ checkCommandIsUnreachable params t =
50685068 when (isUnreachableFunction t
50695069 && (not . any isUnreachableFunction . NE. drop 1 $ getPath (parentMap params) t)
50705070 && (not $ isSourced params t)
5071- && (not $ isFunctionInvokedInReachableCode name)) $
5071+ && (not $ isFunctionReferencedInCommandSubstitution name)) $
50725072 info id 2329 " This function is never invoked. Check usage (or ignored if invoked indirectly)."
50735073 _ -> return ()
50745074 where
@@ -5082,17 +5082,43 @@ checkCommandIsUnreachable params t =
50825082 state <- CF. getIncomingState cfga (getId t)
50835083 return . not $ CF. stateIsReachable state
50845084
5085- -- Check if a function is invoked anywhere in reachable code
5086- isFunctionInvokedInReachableCode :: String -> Bool
5087- isFunctionInvokedInReachableCode name =
5088- any isFunctionCall $ analyse findCalls (rootNode params)
5085+ -- Check if a function is referenced in command substitution or prompt variables
5086+ isFunctionReferencedInCommandSubstitution :: String -> Bool
5087+ isFunctionReferencedInCommandSubstitution name =
5088+ not . null $ analyse findReferences (rootNode params)
50895089 where
5090- findCalls token = when (isFunctionCall token) $ modify (token: )
5091- isFunctionCall token =
5090+ findReferences token =
5091+ case token of
5092+ -- Check in $() command substitutions
5093+ T_DollarExpansion _ _ ->
5094+ when (hasFunctionCall token) $ modify (token: )
5095+ -- Check in backtick command substitutions
5096+ T_Backticked _ _ ->
5097+ when (hasFunctionCall token) $ modify (token: )
5098+ -- Check in assignments to prompt variables (PS1, PS2, PS3, PS4, PROMPT_COMMAND)
5099+ -- These variables are evaluated by bash even in single quotes
5100+ T_Assignment _ _ varname _ value ->
5101+ when (varname `elem` promptVars && hasFunctionReference value) $ modify (token: )
5102+ _ -> return ()
5103+ promptVars = [" PS1" , " PS2" , " PS3" , " PS4" , " PROMPT_COMMAND" ]
5104+ hasFunctionCall token =
5105+ not . null $ analyse findFunctionCalls token
5106+ hasFunctionReference token =
5107+ -- Check if function name appears in the value
5108+ -- For prompt variables, they can contain:
5109+ -- 1. Direct command names (PROMPT_COMMAND='funcname')
5110+ -- 2. Command substitutions with $(...) or `...`
5111+ case getLiteralString token of
5112+ Just str ->
5113+ name == str -- Direct function name
5114+ || (" $(" ++ name) `isInfixOf` str -- $(funcname)
5115+ || (" `" ++ name) `isInfixOf` str -- `funcname`
5116+ Nothing -> hasFunctionCall token
5117+ findFunctionCalls token =
50925118 case token of
50935119 T_SimpleCommand _ _ (cmd: _) ->
5094- getUnquotedLiteral cmd == Just name && not (isUnreachable token)
5095- _ -> False
5120+ when ( getUnquotedLiteral cmd == Just name) $ modify ( token: )
5121+ _ -> return ()
50965122
50975123
50985124prop_checkOverwrittenExitCode1 = verify checkOverwrittenExitCode " x; [ $? -eq 1 ] || [ $? -eq 2 ]"
0 commit comments