Add extra checks for masked return codes#2320
Conversation
|
CC @uri-canva |
|
I think this would need to cover quite a bit more to live up to its name, such as |
|
@koalaman I think it highlights another issue with my chosen name: It's that the check isn't related to |
|
SGTM. In this case it should recognize |
set -e being ignored in process substitutions|
@koalaman PTAL |
|
|
||
| isMaskDeliberate t = hasParent isOrIf t | ||
| where | ||
| isOrIf _ (T_OrIf _ _ right) = ["true"] == oversimplify right |
There was a problem hiding this comment.
oversimplify wasn't as clever or useful as I thought it would be 10 years ago. It would be better to use getCommandBasename :: Token -> Maybe String which is smarter and less heavy handed.
| import ShellCheck.AST | ||
| import ShellCheck.ASTLib | ||
| import ShellCheck.AnalyzerLib hiding (producesComments) | ||
| import ShellCheck.Checks.Commands (declaringCommands) |
There was a problem hiding this comment.
You could move declaringCommands into ShellCheck.Data instead to avoid this dependency and hopefully allow better build parallelization.
| | isMaskDeliberate t = return () | ||
| | isCheckedElsewhere t = return () | ||
| | isMasked t = info (getId t) 2312 | ||
| "Invoke command separately to avoid masking its return value." |
There was a problem hiding this comment.
Maybe add " (or use || true to ignore)."
Invoking separately is not necessarily the best advice for pipes but the wiki can go into detail.
| || isMaskedInHereString t | ||
| || isMaskedInPipe t | ||
| || isMaskedInProcSub t | ||
| || isMaskedInWord t |
There was a problem hiding this comment.
You could instead formulate this as something like
isMasked t = hasParent masker t
where
masker t =
isHereString t
|| hasRightSibblingInPipe t
|| ..
to avoid iterating the same path repeatedly and instead do it in one pass.
| prop_checkExtraMaskedReturns9 = verifyNotTree checkExtraMaskedReturns "echo asdf | false" | ||
| prop_checkExtraMaskedReturns10 = verifyNotTree checkExtraMaskedReturns "readonly x=$(false)" | ||
| prop_checkExtraMaskedReturns11 = verifyTree checkExtraMaskedReturns "readarray -t files < <(ls)" | ||
| checkExtraMaskedReturns params t = runNodeAnalysis checkNode params t |
There was a problem hiding this comment.
So far so good! Here are some cases it misses:
False negative:
x="$(false)$(false)" # Check out getWordParts :: Token -> [Token]
x="$(false)""$(false)" # It takes a T_NormalWord and flattens it into a list of all the quoted and unquoted elements in the string
x=( $(false) $(false) )
x=( $(false) [4]=$(false) )
# These may be less relevant:
cat << foo
$(false)
foo
x=$(( $(date +%s) ))
[[ $(false) ]]
False positive:
x=$(false) y=z # T_SimpleCommand has two assignments in its list in this case
x=$(false) y=$(false) # Only the first one is masked
These are already correctly handled but untested:
echo $(( $(date +%s) ))
x=`false``false`
x=( $(false) )
There was a problem hiding this comment.
Thanks for all these test cases. Any suggestion as to how I should approach this?
x=$(false) y=$(false) # Only the first one is maskedI was thinking that once I find a T_SimpleCommand, I'd traverse the parse tree upwards, looking for a T_Assignment. Once I find that, I'd look at the assignments on the the left of the T_SimpleCommand and check to see if their descendants contain any T_SimpleCommand nodes. The trouble is that it's not obvious to me how I can iterate over the descendants of a node without having a huge case ... of statement which essentially handles each rule in Bash's grammar.
There was a problem hiding this comment.
This big case...of statement already exists. There's a doAnalysis which invokes each node in a tree with a monadic action. The obvious monad is Cont, but to avoid importing that, perhaps Maybe:
containsSimpleCommand rootnode = isNothing $ doAnalysis (\c -> case c of T_SimpleCommand {} -> fail ""; _ -> return ()) rootnode
Currently the code finds the potentially masked T_SimpleCommand, and tries to determine if it is masked. This has some issues like echo $(t=$(false); echo $t) emitting two warnings even though only one is actually masked, and echo $([[ x ]]) emits nothing since it's a compound command instead of a simple command.
An alternative approach is finding the potential masker, e.g. a T_ProcSub, or a T_SimpleCommand _ [] (cmd:args) where any isCommandSubstitution $ concatMap getWordParts args. Since it seems like this is basically required to recognize the context anyways, maybe this is easier.
|
@koalaman How am I going? |
|
Looks like this PR will address #1733. Not sure if that's a good thing though; is it okay for it to be an optional check? |
koalaman
left a comment
There was a problem hiding this comment.
Looks pretty good and feature complete to me!
Did you run this on a larger codebase? What was your experience?
| prop_checkExtraMaskedReturns3 = verifyTree checkExtraMaskedReturns "ls >(cat)" | ||
| prop_checkExtraMaskedReturns4 = verifyTree checkExtraMaskedReturns "false | true" | ||
| prop_checkExtraMaskedReturns5 = verifyNotTree checkExtraMaskedReturns "set -o pipefail; false | true" | ||
| prop_checkExtraMaskedReturns6 = verifyNotTree checkExtraMaskedReturns "false | true || true" |
There was a problem hiding this comment.
You could also include a check for { false || true; } | true to make sure you can suppress individual stages in the pipeline too (it already works as intended)
| isMaskDeliberate t = hasParent isOrIf t | ||
| where | ||
| isOrIf _ (T_OrIf _ _ (T_Pipeline _ _ [T_Redirecting _ _ cmd])) | ||
| = getCommandBasename cmd == Just "true" |
There was a problem hiding this comment.
It should probably support ":" as well since people will likely be writing this a lot :P
| prop_checkExtraMaskedReturns21 = verifyTree checkExtraMaskedReturns "cat << foo\n $(false)\nfoo" | ||
| prop_checkExtraMaskedReturns22 = verifyTree checkExtraMaskedReturns "[[ $(false) ]]" | ||
| prop_checkExtraMaskedReturns23 = verifyNotTree checkExtraMaskedReturns "x=$(false) y=z" | ||
| prop_checkExtraMaskedReturns24 = verifyNotTree checkExtraMaskedReturns "x=$(( $(date +%s) ))" |
There was a problem hiding this comment.
Turns out I was wrong about this one, sorry about that:
$ x=$(( $(echo 1; exit 42) )); echo $?
42
|
|
||
| findMaskedNodes t | isHarmlessCommand t = return () | ||
| findMaskedNodes t | isCheckedElsewhere t = return () | ||
| findMaskedNodes t | isMaskDeliberate t = return () |
There was a problem hiding this comment.
It's not as pretty but you could limit these extra checks to relevant nodes with something like
isRelevant t = not (isHarmlessCommand t || isCheckedElsewhere t || isMaskDeliberate t)
findMaskedNodes t@(T_SimpleCommand _ _ (_:_)) = when (isRelevant t) $ inform t
findMaskedNodes t@T_Condition {} | isRelevant t= when (isRelevant t) $ inform t
This drops total ShellCheck-wide CPU usage by about 5%
@koalaman Yeah, that's why I added these tests: shellcheck/src/ShellCheck/Analytics.hs Lines 4772 to 4775 in 56ddb5f But actually I should run it once more and eyeball the results again now that I've made those tweaks. Also, to make the test Edit 1: I ran it again on our monorepo. I'm seeing lots of errors about |
I would avoid this kind of overfitting TBH. It's often better for the test to be predictable, even if it makes it slightly more inaccurate. "dirname and basename are ignored since they're rarely sources of failure" is simple and easy, while it's harder to build a mental model given observations like:
Other than that it looks good to me. Squash for merge? PS: Any thoughts on a more convincing doc example? Maybe something like |
|
Squashed
Makes sense. I reverted 77f393e (the commit to specifically silence
👍 |
|
Thanks! I've merged it and added a wiki page and changelog entry. |
|
You might also want to consider closing #1733 |
|
@DoxasticFox Sure. There are a few minor issues I'd like to squeeze in first, including #2362 to extend this warning to |
|
@DoxasticFox v0.8.0 is now out |
Merge of upstream through the stable 0.8.0 release.
Conflicts:
ShellCheck.cabal
src/ShellCheck/Analytics.hs
src/ShellCheck/AnalyzerLib.hs
Manual changes:
Moved maskedReturn isPortageBuild check into Commands.hs
Changelog:
----------------------------------------------------------------
Christian Nassif-Haynes (2):
Show info about `set -e` suppression during function calls
Add extra checks for masked return codes
Fabian Wolff (1):
Do not suggest `grep -c` as a replacement for `grep -l/-L | wc -l`
Jens Petersen (1):
move readme to extra-doc-files and add changelog to releases
Kamil Cukrowski (1):
Add a comma to function characters
Matthias Diener (1):
Clarify 'which'
Rebecca Cran (1):
Fix typo in SC2006 message: "backticked" vs "backticks"
Vidar Holen (81):
Merge pull request koalaman#2181 from matthiasdiener/patch-1
Make x-comparison warning default
Stable version v0.7.2
Post-release CHANGELOG update
Update Cabal version for Hackage
Add wait between GitHub and Docker to allow replication
Fix haddock failures (fixes koalaman#2216)
Treat ${arr[*]} like $* for SC2048
Fix bad warning for ${#arr[*]}. Fixes koalaman#2218.
Sanity check command names (fixes koalaman#2227)
Merge pull request koalaman#2241 from Kamilcuk/master
Merge pull request koalaman#2238 from bcran/legacy-backticks-msg
Merge pull request koalaman#2234 from juhp/patch-1
SC2181: Add '!' in suggestion as appropriate (fixes koalaman#2189)
Add :/. to chars recognized for \alias suppression (fixes koalaman#2287)
Don't warn when line starts with &> (fixes koalaman#2281)
Re-add warnings about 'declare var = value' (fixes koalaman#2279)
Don't warn about repeated range in [[ -v arr[xxx] ]] (fixes koalaman#2285)
Don't print colors when $TERM is 'dumb' or unset (fixes koalaman#2260)
Have SC2155 trigger on 'typeset' as well (fixes koalaman#2262)
Warn about quoting in assignments to sh declaration utilities (fixes koalaman#1556)
Fix broken test from previous commit
Warn about unquoted blanks in echo (fixes koalaman#377)
Allow printf/return/assignments after exec (fixes koalaman#2249)
Don't consider [ -n/-z/-v $var ] assignments for subshell modification (fixes koalaman#2217)
Optionally suggest [[ over [ in Bash scripts (-o require-double-brackets) (fixes koalaman#887)
Avoid trigger SC2181 on composite $? checks (fixes koalaman#1167)
Warn about eval'ing arrays
Merge pull request koalaman#2289 from nafigator/master
SC2295 Warn about unquoted variables in PE patterns (fixes koalaman#2290)
Switch build status badge from TravisCI to GitHub
Remove defunct SonarQube plugin link (fixes koalaman#2292)
Extend warnings about spaces around = to 'let'
Suppress SC2167 when name is "_" (fixes koalaman#2298)
Improve warnings for bad parameter expansion (fixes koalaman#2297)
Warn about looping over array values and using them as keys
Don't warn about variables guarded with :+ (fixes koalaman#2296)
Recognize wait -p as assigning a variable (fixes koalaman#2179)
Improve warnings for expr (fixes koalaman#2033)
Add `rg` to list of commands ignored for SC2016 (fixes koalaman#2209)
Don't warn about unused variables starting with _ (fixes koalaman#1498)
Merge pull request koalaman#2307 from a1346054/fixes
Fix parsing of [$var] (fixes koalaman#2309)
Allow running this repo as a pre-commit hook
Revert "Allow running this repo as a pre-commit hook"
Add pre-commit instructions
Add shellcheck-precommit hook to README.md
Improve warnings about unnecessary subshells (fixes koalaman#2169)
Warn about strings for numerical operators in [[ ]] (fixes koalaman#2312)
Merge pull request koalaman#2303 from DoxasticFox/set-e-functions
Allow specifying external-sources=true in shellcheckrc (fixes koalaman#1818)
Merge pull request koalaman#2318 from FabianWolff/grep-lL-wc-l
Allow `disable=all` to disable all warnings (fixes koalaman#2323)
Remove SC1004 (fixes koalaman#2326)
Suppress SC2094 when both are input redirections (fixes koalaman#2325)
Don't trigger SC2140 on ${x+"a" "b"} (fixes koalaman#2265)
Strip lines containing "STRIP" from ./striptests
Add a `setgitversion` script to update the version string with git
The removed check was SC1004, not SC1003
Disable UUOC for cat with unquoted variable (fixes koalaman#2333)
Don't emit SC2140 when trapped string is /, = or : (fixes koalaman#2334)
Merge pull request koalaman#2320 from DoxasticFox/set-e-proc-sub
Mention check-extra-masked-returns in changelog
Add suggestion level in text for TTY output (fixes koalaman#2339)
Mention require-double-brackets in CHANGELOG
Warn about `read foo[i]` expanding as glob (fixes koalaman#2345)
For `while getopts; do case ..` checks, make sure variable matches
Skip SC2214 if variable is modified in loop (fixes koalaman#2351)
Mention known incompatibilities in man page
Treat typeset similar to declare (fixes koalaman#2354)
Give more examples of what ShellCheck looks for
Have quickscripts search for relevant paths (fixes koalaman#2286)
Warn about [^..] in Dash (fixes koalaman#2361)
Consider all forms of TA_Assignment to remove spaces (fixes koalaman#2364)
Include `local -r` in check-extra-masked-returns (fixes koalaman#2362)
Update release checklist
Update distro tests
Update stack resolver
Update copyright years
Fix bad version on stable releases
Stable version 0.8.0
Yancharuk Alexander (2):
Minor changes in README
Review fixes in README
a1346054 (2):
Fix redirect in LICENSE file
Remove trailing whitespace
.github/workflows/build.yml | 27 +-
.github_deploy | 1 -
CHANGELOG.md | 51 +-
LICENSE | 2 +-
README.md | 38 +-
ShellCheck.cabal | 12 +-
quickrun | 10 +-
quicktest | 11 +-
setgitversion | 11 +
shellcheck.1.md | 50 +-
shellcheck.hs | 24 +-
snap/snapcraft.yaml | 4 +-
src/ShellCheck/AST.hs | 1 +
src/ShellCheck/ASTLib.hs | 74 ++-
src/ShellCheck/Analytics.hs | 841 ++++++++++++++++++++++++++++-----
src/ShellCheck/AnalyzerLib.hs | 182 +++++--
src/ShellCheck/Checker.hs | 84 +++-
src/ShellCheck/Checks/Commands.hs | 271 ++++++++++-
src/ShellCheck/Checks/ShellSupport.hs | 6 +-
src/ShellCheck/Data.hs | 4 +-
src/ShellCheck/Formatter/CheckStyle.hs | 2 +-
src/ShellCheck/Formatter/Diff.hs | 5 +-
src/ShellCheck/Formatter/Format.hs | 21 +-
src/ShellCheck/Formatter/GCC.hs | 2 +-
src/ShellCheck/Formatter/JSON1.hs | 2 +-
src/ShellCheck/Formatter/TTY.hs | 4 +-
src/ShellCheck/Interface.hs | 14 +-
src/ShellCheck/Parser.hs | 76 ++-
stack.yaml | 2 +-
striptests | 2 +-
test/buildtest | 2 +
test/check_release | 8 +-
test/distrotest | 10 +-
test/stacktest | 5 +-
34 files changed, 1553 insertions(+), 306 deletions(-)
create mode 100755 setgitversion
BUG=b:259131253
TEST=stack test; Compare cros lint checks on ebuilds
Change-Id: I5ca3fb27faa59b2f11369c2a150a419dee289977
Adds the check mentioned here, plus related ones: #2303 (comment) Example:
/tmp/a.sh: