diff --git a/src/ShellCheck/ASTLib.hs b/src/ShellCheck/ASTLib.hs index 6cf48cb..aa73c79 100644 --- a/src/ShellCheck/ASTLib.hs +++ b/src/ShellCheck/ASTLib.hs @@ -544,33 +544,18 @@ getCommandNameAndToken direct t = fromMaybe (Nothing, t) $ do return t _ -> fail "" --- If a command substitution is a single command, get its name. --- $(date +%s) = Just "date" -getCommandNameFromExpansion :: Token -> Maybe String -getCommandNameFromExpansion t = +-- If a command substitution is a single SimpleCommand, return it. +getSimpleCommandFromExpansion :: Token -> Maybe Token +getSimpleCommandFromExpansion t = case t of T_DollarExpansion _ [c] -> extract c T_Backticked _ [c] -> extract c T_DollarBraceCommandExpansion _ [c] -> extract c _ -> Nothing where - extract (T_Pipeline _ _ [cmd]) = getCommandName cmd + extract (T_Pipeline _ _ [c]) = getCommand c extract _ = Nothing --- If a command substitution is a single command, get its argument Tokens. --- Return an empty list if there are no arguments or the token is not a command substitution. --- $(date +%s) = ["+%s"] -getArgumentsFromExpansion :: Token -> [Token] -getArgumentsFromExpansion t = - case t of - T_DollarExpansion _ [c] -> extract c - T_Backticked _ [c] -> extract c - T_DollarBraceCommandExpansion _ [c] -> extract c - _ -> [] - where - extract (T_Pipeline _ _ [cmd]) = arguments cmd - extract _ = [] - -- Get the basename of a token representing a command getCommandBasename = fmap basename . getCommandName @@ -928,5 +913,20 @@ getEnableDirectives root = T_Annotation _ list _ -> [s | EnableComment s <- list] _ -> [] + +commandExpansionShouldBeSplit t = do + cmd <- getSimpleCommandFromExpansion t + name <- getCommandName cmd + case () of + -- Should probably be split + _ | name `elem` ["seq", "pgrep"] -> return True + -- Portage macros that return a single word or nothing + _ | name `elem` ["usev", "use_with", "use_enable"] -> return True + -- Portage macros that are fine as long as the arguments have no spaces + _ | name `elem` ["usex", "meson_use", "meson_feature"] -> do + return . not $ any (' ' `elem`) $ map (getLiteralStringDef " ") $ arguments cmd + _ -> Nothing + + return [] runTests = $quickCheckAll diff --git a/src/ShellCheck/Analytics.hs b/src/ShellCheck/Analytics.hs index 58c3310..51015d2 100644 --- a/src/ShellCheck/Analytics.hs +++ b/src/ShellCheck/Analytics.hs @@ -792,6 +792,11 @@ prop_checkUnquotedExpansions8 = verifyNot checkUnquotedExpansions "set -- $(seq prop_checkUnquotedExpansions9 = verifyNot checkUnquotedExpansions "echo foo `# inline comment`" prop_checkUnquotedExpansions10 = verify checkUnquotedExpansions "#!/bin/sh\nexport var=$(val)" prop_checkUnquotedExpansions11 = verifyNot checkUnquotedExpansions "ps -p $(pgrep foo)" +prop_checkUnquotedExpansions12 = verify checkUnquotedExpansions "#!/bin/sh\nexport var=$(val)" +prop_checkUnquotedExpansions13 = verifyNot checkUnquotedExpansions "echo $(usev X)" +prop_checkUnquotedExpansions14 = verifyNot checkUnquotedExpansions "echo $(usex X \"\" Y)" +prop_checkUnquotedExpansions15 = verify checkUnquotedExpansions "echo $(usex X \"Y Z\" W)" + checkUnquotedExpansions params = check where @@ -801,12 +806,9 @@ checkUnquotedExpansions params = check _ = return () tree = parentMap params examine t contents = - unless (null contents || shouldBeSplit t || isQuoteFree (shellType params) tree t || usedAsCommandName tree t) $ + unless (null contents || commandExpansionShouldBeSplit t == Just True || isQuoteFree (shellType params) tree t || usedAsCommandName tree t) $ warn (getId t) 2046 "Quote this to prevent word splitting." - shouldBeSplit t = - getCommandNameFromExpansion t `elem` [Just "seq", Just "pgrep"] - prop_checkRedirectToSame = verify checkRedirectToSame "cat foo > foo" prop_checkRedirectToSame2 = verify checkRedirectToSame "cat lol | sed -e 's/a/b/g' > lol" @@ -1037,9 +1039,6 @@ checkStderrRedirect params redir@(T_Redirecting _ [ checkStderrRedirect _ _ = return () -lt x = trace ("Tracing " ++ show x) x -- STRIP -ltt t = trace ("Tracing " ++ show t) -- STRIP - prop_checkSingleQuotedVariables = verify checkSingleQuotedVariables "echo '$foo'" prop_checkSingleQuotedVariables2 = verify checkSingleQuotedVariables "echo 'lol$1.jpg'" @@ -1070,6 +1069,10 @@ prop_checkSingleQuotedVariables22 = verifyNot checkSingleQuotedVariables "jq '$_ prop_checkSingleQuotedVariables23 = verifyNot checkSingleQuotedVariables "command jq '$__loc__'" prop_checkSingleQuotedVariables24 = verifyNot checkSingleQuotedVariables "exec jq '$__loc__'" prop_checkSingleQuotedVariables25 = verifyNot checkSingleQuotedVariables "exec -c -a foo jq '$__loc__'" +prop_checkSingleQuotedVariablesCros1 = verifyNot checkSingleQuotedVariables "python_gen_any_dep 'dev-python/pyyaml[${PYTHON_USEDEP}]'" +prop_checkSingleQuotedVariablesCros2 = verifyNot checkSingleQuotedVariables "python_gen_cond_dep 'dev-python/unittest2[${PYTHON_USEDEP}]' python2_7 pypy" +prop_checkSingleQuotedVariablesCros3 = verifyNot checkSingleQuotedVariables "version_format_string '${PN}_source_$1_$2-$3_$4'" + checkSingleQuotedVariables params t@(T_SingleQuoted id s) = @@ -1109,6 +1112,9 @@ checkSingleQuotedVariables params t@(T_SingleQuoted id s) = ,"git filter-branch" ,"mumps -run %XCMD" ,"mumps -run LOOP%XCMD" + ,"python_gen_any_dep" + ,"python_gen_cond_dep" + ,"version_format_string" ] || "awk" `isSuffixOf` commandName || "perl" `isPrefixOf` commandName @@ -3509,6 +3515,7 @@ prop_checkSplittingInArrays5 = verifyNot checkSplittingInArrays "a=( $! $$ $# )" prop_checkSplittingInArrays6 = verifyNot checkSplittingInArrays "a=( ${#arr[@]} )" prop_checkSplittingInArrays7 = verifyNot checkSplittingInArrays "a=( foo{1,2} )" prop_checkSplittingInArrays8 = verifyNot checkSplittingInArrays "a=( * )" +prop_checkSplittingInArrays9 = verifyNot checkSplittingInArrays "a=( $(use_enable foo) )" checkSplittingInArrays params t = case t of T_Array _ elements -> mapM_ check elements @@ -3518,6 +3525,7 @@ checkSplittingInArrays params t = T_NormalWord _ parts -> mapM_ checkPart parts _ -> return () checkPart part = case part of + _ | commandExpansionShouldBeSplit part == Just True -> return () T_DollarExpansion id _ -> forCommand id T_DollarBraceCommandExpansion id _ -> forCommand id T_Backticked id _ -> forCommand id diff --git a/src/ShellCheck/AnalyzerLib.hs b/src/ShellCheck/AnalyzerLib.hs index 3ceb245..28cbf1b 100644 --- a/src/ShellCheck/AnalyzerLib.hs +++ b/src/ShellCheck/AnalyzerLib.hs @@ -604,6 +604,17 @@ getReferencedVariableCommand base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Litera head:_ -> map (\x -> (base, head, x)) $ getVariablesFromLiteralToken head _ -> [] "alias" -> [(base, token, name) | token <- rest, name <- getVariablesFromLiteralToken token] + + -- tc-export makes a list of toolchain variables available, similar to export. + -- Usage tc-export CC CXX + "tc-export" -> concatMap getReference rest + + -- tc-export_build_env exports the listed variables plus a bunch of BUILD_XX variables. + -- Usage tc-export_build_env BUILD_CC + "tc-export_build_env" -> + concatMap getReference rest + ++ [ (base, base, v) | v <- portageBuildEnvVariables ] + _ -> [] where forDeclare = @@ -675,6 +686,16 @@ getModifiedVariableCommand base@(T_SimpleCommand id cmdPrefix (T_NormalWord _ (T "DEFINE_integer" -> maybeToList $ getFlagVariable rest "DEFINE_string" -> maybeToList $ getFlagVariable rest + "tc-export" -> concatMap getModifierParamString rest + + -- tc-export_build_env exports the listed variables plus a bunch of BUILD_XX variables. + -- Usage tc-export_build_env BUILD_CC + "tc-export_build_env" -> + concatMap getModifierParamString rest + ++ [ (base, base, var, DataString $ SourceExternal) | + var <- ["BUILD_" ++ x, x ++ "_FOR_BUILD" ], + x <- portageBuildEnvVariables ] + _ -> [] where flags = map snd $ getAllFlags base diff --git a/src/ShellCheck/Data.hs b/src/ShellCheck/Data.hs index 550ff87..fff27be 100644 --- a/src/ShellCheck/Data.hs +++ b/src/ShellCheck/Data.hs @@ -62,8 +62,92 @@ internalVariables = [ , "FLAGS_ARGC", "FLAGS_ARGV", "FLAGS_ERROR", "FLAGS_FALSE", "FLAGS_HELP", "FLAGS_PARENT", "FLAGS_RESERVED", "FLAGS_TRUE", "FLAGS_VERSION", "flags_error", "flags_return" + ] ++ portageManualInternalVariables + + +portageManualInternalVariables = [ + -- toolchain settings + "CFLAGS", "CXXFLAGS", "CPPFLAGS", "LDFLAGS", "FFLAGS", "FCFLAGS", + "CBUILD", "CHOST", "MAKEOPTS", + -- TODO: Delete these if we can handle `tc-export CC` implicit export. + "CC", "CPP", "CXX", + + -- portage internals + "EBUILD_PHASE", "EBUILD_SH_ARGS", "EMERGE_FROM", "FILESDIR", + "MERGE_TYPE", "PM_EBUILD_HOOK_DIR", "PORTAGE_ACTUAL_DISTDIR", + "PORTAGE_ARCHLIST", "PORTAGE_BASHRC", "PORTAGE_BINPKG_FILE", + "PORTAGE_BINPKG_TAR_OPTS", "PORTAGE_BINPKG_TMPFILE", "PORTAGE_BIN_PATH", + "PORTAGE_BUILDDIR", "PORTAGE_BUILD_GROUP", "PORTAGE_BUILD_USER", + "PORTAGE_BUNZIP2_COMMAND", "PORTAGE_BZIP2_COMMAND", "PORTAGE_COLORMAP", + "PORTAGE_CONFIGROOT", "PORTAGE_DEBUG", "PORTAGE_DEPCACHEDIR", + "PORTAGE_EBUILD_EXIT_FILE", "PORTAGE_ECLASS_LOCATIONS", "PORTAGE_GID", + "PORTAGE_GRPNAME", "PORTAGE_INST_GID", "PORTAGE_INST_UID", + "PORTAGE_INTERNAL_CALLER", "PORTAGE_IPC_DAEMON", "PORTAGE_IUSE", + "PORTAGE_LOG_FILE", "PORTAGE_MUTABLE_FILTERED_VARS", + "PORTAGE_OVERRIDE_EPREFIX", "PORTAGE_PYM_PATH", "PORTAGE_PYTHON", + "PORTAGE_PYTHONPATH", "PORTAGE_READONLY_METADATA", "PORTAGE_READONLY_VARS", + "PORTAGE_REPO_NAME", "PORTAGE_REPOSITORIES", "PORTAGE_RESTRICT", + "PORTAGE_SAVED_READONLY_VARS", "PORTAGE_SIGPIPE_STATUS", "PORTAGE_TMPDIR", + "PORTAGE_UPDATE_ENV", "PORTAGE_USERNAME", "PORTAGE_VERBOSE", + "PORTAGE_WORKDIR_MODE", "PORTAGE_XATTR_EXCLUDE", "REPLACING_VERSIONS", + "REPLACED_BY_VERSION", "__PORTAGE_HELPER", "__PORTAGE_TEST_HARDLINK_LOCKS", + + -- generic ebuilds + "A", "ARCH", "BDEPEND", "BOARD_USE", "BROOT", "CATEGORY", "D", + "DEFINED_PHASES", "DEPEND", "DESCRIPTION", "DISTDIR", "DOCS", "EAPI", + "ECLASS", "ED", "EPREFIX", "EROOT", "ESYSROOT", "EXTRA_ECONF", + "EXTRA_EINSTALL", "EXTRA_MAKE", "FEATURES", "FILESDIR", "HOME", "HOMEPAGE", + "HTML_DOCS", "INHERITED", "IUSE", "KEYWORDS", "LICENSE", "P", "PATCHES", + "PDEPEND", "PF", "PKG_INSTALL_MASK", "PKGUSE", "PN", "PR", "PROPERTIES", + "PROVIDES_EXCLUDE", "PV", "PVR", "QA_AM_MAINTAINER_MODE", + "QA_CONFIGURE_OPTIONS", "QA_DESKTOP_FILE", "QA_DT_NEEDED", "QA_EXECSTACK", + "QA_FLAGS_IGNORED", "QA_MULTILIB_PATHS", "QA_PREBUILT", "QA_PRESTRIPPED", + "QA_SONAME", "QA_SONAME_NO_SYMLINK", "QA_TEXTRELS", "QA_WX_LOAD", "RDEPEND", + "REPOSITORY", "REQUIRED_USE", "REQUIRES_EXCLUDE", "RESTRICT", "ROOT", "S", + "SLOT", "SRC_TEST", "SRC_URI", "STRIP_MASK", "SUBSLOT", "SYSROOT", "T", + "WORKDIR", + + -- autotest.eclass declared incorrectly + "AUTOTEST_CLIENT_TESTS", "AUTOTEST_CLIENT_SITE_TESTS", + "AUTOTEST_SERVER_TESTS", "AUTOTEST_SERVER_SITE_TESTS", "AUTOTEST_CONFIG", + "AUTOTEST_DEPS", "AUTOTEST_PROFILERS", "AUTOTEST_CONFIG_LIST", + "AUTOTEST_DEPS_LIST", "AUTOTEST_PROFILERS_LIST", + + -- cros-board.eclass declared incorrectly + "CROS_BOARDS", + + -- Undeclared cros-kernel2 vars + "AFDO_PROFILE_VERSION", + + -- haskell-cabal.eclass declared incorrectly + "CABAL_FEATURES", + + -- Undeclared haskell-cabal.eclass vars + "CABAL_CORE_LIB_GHC_PV", + + -- Undeclared readme.gentoo.eclass vars + "DOC_CONTENTS", + + -- Backwards compatibility perl-module.eclass vars + "MODULE_AUTHOR", "MODULE_VERSION", + + -- Undeclared perl-module.eclass vars + "mydoc", + + -- python-utils-r1.eclass declared incorrectly + "RESTRICT_PYTHON_ABIS", "PYTHON_MODNAME", + + -- ABI variables + "ABI", "DEFAULT_ABI", + + -- AFDO variables + "AFDO_LOCATION", + + -- Linguas + "LINGUAS" ] + specialIntegerVariables = [ "$", "?", "!", "#" ] @@ -90,7 +174,9 @@ unbracedVariables = specialVariables ++ [ arrayVariables = [ "BASH_ALIASES", "BASH_ARGC", "BASH_ARGV", "BASH_CMDS", "BASH_LINENO", "BASH_REMATCH", "BASH_SOURCE", "BASH_VERSINFO", "COMP_WORDS", "COPROC", - "DIRSTACK", "FUNCNAME", "GROUPS", "MAPFILE", "PIPESTATUS", "COMPREPLY" + "DIRSTACK", "FUNCNAME", "GROUPS", "MAPFILE", "PIPESTATUS", "COMPREPLY", + -- For Portage + "PATCHES" ] commonCommands = [ @@ -150,6 +236,11 @@ unaryTestOps = [ "-o", "-v", "-R" ] +-- Variables inspected by Portage tc-export_build_env +portageBuildEnvVariables = [ + "CFLAGS", "CXXFLAGS", "CPPFLAGS", "LDFLAGS" + ] + shellForExecutable :: String -> Maybe Shell shellForExecutable name = case name of