-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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 |
src/ShellCheck/Analytics.hs
Outdated
|
||
isMaskDeliberate t = hasParent isOrIf t | ||
where | ||
isOrIf _ (T_OrIf _ _ right) = ["true"] == oversimplify right |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/ShellCheck/Analytics.hs
Outdated
@@ -24,6 +24,7 @@ module ShellCheck.Analytics (runAnalytics, optionalChecks, ShellCheck.Analytics. | |||
import ShellCheck.AST | |||
import ShellCheck.ASTLib | |||
import ShellCheck.AnalyzerLib hiding (producesComments) | |||
import ShellCheck.Checks.Commands (declaringCommands) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could move declaringCommands
into ShellCheck.Data
instead to avoid this dependency and hopefully allow better build parallelization.
src/ShellCheck/Analytics.hs
Outdated
| 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add " (or use || true to ignore)."
Invoking separately is not necessarily the best advice for pipes but the wiki can go into detail.
src/ShellCheck/Analytics.hs
Outdated
|| isMaskedInHereString t | ||
|| isMaskedInPipe t | ||
|| isMaskedInProcSub t | ||
|| isMaskedInWord t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/ShellCheck/Analytics.hs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for all these test cases. Any suggestion as to how I should approach this?
x=$(false) y=$(false) # Only the first one is masked
I 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
src/ShellCheck/Analytics.hs
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out I was wrong about this one, sorry about that:
$ x=$(( $(echo 1; exit 42) )); echo $?
42
src/ShellCheck/Analytics.hs
Outdated
|
||
findMaskedNodes t | isHarmlessCommand t = return () | ||
findMaskedNodes t | isCheckedElsewhere t = return () | ||
findMaskedNodes t | isMaskDeliberate t = return () |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
: