From 8e15bcfc6ed4e6fe1ba90ad937f9f8747d1f56dd 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 3ef1e7c83e3..406df0ec0b4 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -304,10 +304,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; } @@ -442,11 +442,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); @@ -457,13 +457,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; @@ -658,7 +658,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 94f2b721037..fea2a5517a2 100644 --- a/lib/cppcheck.h +++ b/lib/cppcheck.h @@ -142,6 +142,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 84db6db6c28..384d9ee0d26 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -84,6 +84,7 @@ class TestCppcheck : public TestFixture { TEST_CASE(checkPlistOutput); TEST_CASE(premiumResultsCache); TEST_CASE(purgedConfiguration); + TEST_CASE(cmdFileName); } void getErrorMessages() const { @@ -622,6 +623,18 @@ class TestCppcheck : public TestFixture { it->toString(false, templateFormat, "")); } + 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 99272f958463bb77056ed89e4ae7b8edd15cea36 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 fea2a5517a2..e007d40f908 100644 --- a/lib/cppcheck.h +++ b/lib/cppcheck.h @@ -143,7 +143,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 bf41abaa8792521b1c8bb019591b947260d0d35f 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 406df0ec0b4..25a35d94c49 100644 --- a/lib/cppcheck.cpp +++ b/lib/cppcheck.cpp @@ -306,8 +306,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 384d9ee0d26..74cc72ceedd 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -626,13 +626,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