From 7d76eefa659dde38e54acdf9d7aacedee8844e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 12 May 2026 17:01:31 +0200 Subject: [PATCH 1/3] Fix #14749 (cmdFilename: handle more bash special characters) --- lib/cppcheck.cpp | 16 ++++++++-------- lib/cppcheck.h | 5 +++++ test/testcppcheck.cpp | 13 +++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index 8e32e3e3152..cd5caa2017c 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -318,10 +318,10 @@ namespace { }; } -static std::string cmdFileName(std::string f) +std::string CppCheck::cmdFileName(std::string f) { f = Path::toNativeSeparators(std::move(f)); - if (f.find(' ') != std::string::npos) + if (f.find_first_of(" \t;$<>|&`\n") != std::string::npos) return "\"" + f + "\""; return f; } @@ -456,11 +456,11 @@ static std::vector executeAddon(const AddonInfo &addonInfo, std::string pythonExe; if (!addonInfo.executable.empty()) - pythonExe = addonInfo.executable; + pythonExe = CppCheck::cmdFileName(addonInfo.executable); else if (!addonInfo.python.empty()) - pythonExe = cmdFileName(addonInfo.python); + pythonExe = CppCheck::cmdFileName(addonInfo.python); else if (!defaultPythonExe.empty()) - pythonExe = cmdFileName(defaultPythonExe); + pythonExe = CppCheck::cmdFileName(defaultPythonExe); else { // store in static variable so we only look this up once - TODO: do not cache globally static const std::string detectedPythonExe = detectPython(executeCommand); @@ -471,13 +471,13 @@ static std::vector executeAddon(const AddonInfo &addonInfo, std::string args; if (addonInfo.executable.empty()) - args = cmdFileName(addonInfo.runScript) + " " + cmdFileName(addonInfo.scriptFile); + args = CppCheck::cmdFileName(addonInfo.runScript) + " " + CppCheck::cmdFileName(addonInfo.scriptFile); args += std::string(args.empty() ? "" : " ") + "--cli" + addonInfo.args; if (!premiumArgs.empty() && !addonInfo.executable.empty()) args += " " + premiumArgs; const bool is_file_list = (file.find(FILELIST) != std::string::npos); - const std::string fileArg = (is_file_list ? " --file-list " : " ") + cmdFileName(file); + const std::string fileArg = (is_file_list ? " --file-list " : " ") + CppCheck::cmdFileName(file); args += fileArg; std::string result; @@ -672,7 +672,7 @@ static std::string getClangFlags(const Settings& setting, Standards::Language la flags += getDefinesFlags(setting.userDefines); for (const std::string &i: setting.userIncludes) - flags += "--include " + cmdFileName(i) + " "; + flags += "--include " + CppCheck::cmdFileName(i) + " "; return flags; } diff --git a/lib/cppcheck.h b/lib/cppcheck.h index 4ccd68193ea..bbbd78f95f6 100644 --- a/lib/cppcheck.h +++ b/lib/cppcheck.h @@ -144,6 +144,11 @@ class CPPCHECKLIB CppCheck { /** analyse whole program use .analyzeinfo files or ctuinfo string */ unsigned int analyseWholeProgram(const std::string &buildDir, const std::list &files, const std::list& fileSettings, const std::string& ctuInfo); + /** + * + */ + static std::string cmdFileName(std::string f); + private: void purgedConfigurationMessage(const std::string &file, const std::string& configuration); diff --git a/test/testcppcheck.cpp b/test/testcppcheck.cpp index d88daceb0d9..b1a2f896e12 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -102,6 +102,7 @@ class TestCppcheck : public TestFixture { TEST_CASE(premiumResultsCache); TEST_CASE(purgedConfiguration); TEST_CASE(recheckInclude); + TEST_CASE(cmdFileName); } void getErrorMessages() const { @@ -757,6 +758,18 @@ class TestCppcheck : public TestFixture { } } + void cmdFileName() const { + ASSERT_EQUALS("x", CppCheck::cmdFileName("x")); + ASSERT_EQUALS("\" \"", CppCheck::cmdFileName(" ")); + ASSERT_EQUALS("\"\t\"", CppCheck::cmdFileName("\t")); + ASSERT_EQUALS("\";\"", CppCheck::cmdFileName(";")); + ASSERT_EQUALS("\">\"", CppCheck::cmdFileName(">")); + ASSERT_EQUALS("\"<\"", CppCheck::cmdFileName("<")); + ASSERT_EQUALS("\"|\"", CppCheck::cmdFileName("|")); + ASSERT_EQUALS("\"`\"", CppCheck::cmdFileName("`")); + ASSERT_EQUALS("\"$\"", CppCheck::cmdFileName("$")); + } + // TODO: test suppressions // TODO: test all with FS }; From 1e5b50a86982253024b371c197c69916237fc9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Tue, 12 May 2026 17:03:33 +0200 Subject: [PATCH 2/3] comment --- lib/cppcheck.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cppcheck.h b/lib/cppcheck.h index bbbd78f95f6..d524d668a51 100644 --- a/lib/cppcheck.h +++ b/lib/cppcheck.h @@ -145,7 +145,7 @@ class CPPCHECKLIB CppCheck { unsigned int analyseWholeProgram(const std::string &buildDir, const std::list &files, const std::list& fileSettings, const std::string& ctuInfo); /** - * + * Return quoted filename string if input filename contains spaces or various other shell meta characters. */ static std::string cmdFileName(std::string f); From fff2c109390cd54e379f24cb35cf4237970999c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 15 May 2026 20:17:13 +0200 Subject: [PATCH 3/3] disallow certain characters --- lib/cppcheck.cpp | 16 +++++++++++++++- test/testcppcheck.cpp | 16 +++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/cppcheck.cpp b/lib/cppcheck.cpp index cd5caa2017c..0206f8abbcb 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -320,8 +320,22 @@ namespace { std::string CppCheck::cmdFileName(std::string f) { + // do not allow characters that potentially has a special meaning for the shell + const auto badpos = f.find_first_of("\t\n\r;$<>|&`"); + if (badpos != std::string::npos) { + std::string c; + if (f[badpos] == '\n') + c = ""; + else if (f[badpos] == '\r') + c = ""; + else if (f[badpos] == '\t') + c = ""; + else + c += f[badpos]; + throw std::runtime_error("Cppcheck does not allow character " + c + " in filename " + f); + } f = Path::toNativeSeparators(std::move(f)); - if (f.find_first_of(" \t;$<>|&`\n") != std::string::npos) + if (f.find(' ') != std::string::npos) return "\"" + f + "\""; return f; } diff --git a/test/testcppcheck.cpp b/test/testcppcheck.cpp index b1a2f896e12..031ecad4c5f 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -761,13 +761,15 @@ class TestCppcheck : public TestFixture { void cmdFileName() const { ASSERT_EQUALS("x", CppCheck::cmdFileName("x")); ASSERT_EQUALS("\" \"", CppCheck::cmdFileName(" ")); - ASSERT_EQUALS("\"\t\"", CppCheck::cmdFileName("\t")); - ASSERT_EQUALS("\";\"", CppCheck::cmdFileName(";")); - ASSERT_EQUALS("\">\"", CppCheck::cmdFileName(">")); - ASSERT_EQUALS("\"<\"", CppCheck::cmdFileName("<")); - ASSERT_EQUALS("\"|\"", CppCheck::cmdFileName("|")); - ASSERT_EQUALS("\"`\"", CppCheck::cmdFileName("`")); - ASSERT_EQUALS("\"$\"", CppCheck::cmdFileName("$")); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName("\t"), std::runtime_error, "Cppcheck does not allow character in filename \t"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName("\r"), std::runtime_error, "Cppcheck does not allow character in filename \r"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName("\n"), std::runtime_error, "Cppcheck does not allow character in filename \n"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName(";"), std::runtime_error, "Cppcheck does not allow character ; in filename ;"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName(">"), std::runtime_error, "Cppcheck does not allow character > in filename >"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName("<"), std::runtime_error, "Cppcheck does not allow character < in filename <"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName("|"), std::runtime_error, "Cppcheck does not allow character | in filename |"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName("`"), std::runtime_error, "Cppcheck does not allow character ` in filename `"); + ASSERT_THROW_EQUALS(CppCheck::cmdFileName("$"), std::runtime_error, "Cppcheck does not allow character $ in filename $"); } // TODO: test suppressions