From 11993ed99928baad308fe952e5ee744ec70267c2 Mon Sep 17 00:00:00 2001 From: Aleksandr Serbin Date: Fri, 24 Jan 2020 08:06:09 +0200 Subject: [PATCH] Ticket 5607: Allow to exclude folders with glob pattern (#2498) --- cli/cppcheckexecutor.cpp | 4 +- lib/importproject.cpp | 4 ++ lib/suppressions.cpp | 79 +------------------------------ lib/suppressions.h | 1 - lib/utils.h | 82 +++++++++++++++++++++++++++++++++ test/testimportproject.cpp | 19 ++++++++ test/testrunner.vcxproj | 1 + test/testrunner.vcxproj.filters | 3 ++ test/testsuppressions.cpp | 19 -------- test/testutils.cpp | 72 +++++++++++++++++++++++++++++ 10 files changed, 184 insertions(+), 100 deletions(-) create mode 100644 test/testutils.cpp diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index c4b91e113..663cc78ef 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -164,7 +164,7 @@ bool CppCheckExecutor::parseFromArgs(CppCheck *cppcheck, int argc, const char* c std::list newList; for (const ImportProject::FileSettings &fsetting : settings.project.fileSettings) { - if (Suppressions::matchglob(mSettings->fileFilter, fsetting.filename)) { + if (matchglob(mSettings->fileFilter, fsetting.filename)) { newList.push_back(fsetting); } } @@ -189,7 +189,7 @@ bool CppCheckExecutor::parseFromArgs(CppCheck *cppcheck, int argc, const char* c } else if (!mSettings->fileFilter.empty()) { std::map newMap; for (std::map::const_iterator i = mFiles.begin(); i != mFiles.end(); ++i) - if (Suppressions::matchglob(mSettings->fileFilter, i->first)) { + if (matchglob(mSettings->fileFilter, i->first)) { newMap[i->first] = i->second; } mFiles = newMap; diff --git a/lib/importproject.cpp b/lib/importproject.cpp index 7ea5f2642..0cb8af05f 100644 --- a/lib/importproject.cpp +++ b/lib/importproject.cpp @@ -43,6 +43,10 @@ void ImportProject::ignorePaths(const std::vector &ipaths) ignore = true; break; } + if (isValidGlobPattern(i) && matchglob(i, it->filename)) { + ignore = true; + break; + } if (!Path::isAbsolute(i)) { i = mPath + i; if (it->filename.size() > i.size() && it->filename.compare(0,i.size(),i)==0) { diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index 2bfb1e772..97ad92b53 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -21,30 +21,17 @@ #include "errorlogger.h" #include "mathlib.h" #include "path.h" +#include "utils.h" #include #include #include // std::isdigit, std::isalnum, etc -#include #include #include class ErrorLogger; -static bool isValidGlobPattern(const std::string &pattern) -{ - for (std::string::const_iterator i = pattern.begin(); i != pattern.end(); ++i) { - if (*i == '*' || *i == '?') { - std::string::const_iterator j = i + 1; - if (j != pattern.end() && (*j == '*' || *j == '?')) { - return false; - } - } - } - return true; -} - static bool isAcceptedErrorIdChar(char c) { switch (c) { @@ -354,67 +341,3 @@ std::list Suppressions::getUnmatchedGlobalSuppression } return result; } - -bool Suppressions::matchglob(const std::string &pattern, const std::string &name) -{ - const char *p = pattern.c_str(); - const char *n = name.c_str(); - std::stack > backtrack; - - for (;;) { - bool matching = true; - while (*p != '\0' && matching) { - switch (*p) { - case '*': - // Step forward until we match the next character after * - while (*n != '\0' && *n != p[1]) { - n++; - } - if (*n != '\0') { - // If this isn't the last possibility, save it for later - backtrack.push(std::make_pair(p, n)); - } - break; - case '?': - // Any character matches unless we're at the end of the name - if (*n != '\0') { - n++; - } else { - matching = false; - } - break; - default: - // Non-wildcard characters match literally - if (*n == *p) { - n++; - } else if (*n == '\\' && *p == '/') { - n++; - } else if (*n == '/' && *p == '\\') { - n++; - } else { - matching = false; - } - break; - } - p++; - } - - // If we haven't failed matching and we've reached the end of the name, then success - if (matching && *n == '\0') { - return true; - } - - // If there are no other paths to try, then fail - if (backtrack.empty()) { - return false; - } - - // Restore pointers from backtrack stack - p = backtrack.top().first; - n = backtrack.top().second; - backtrack.pop(); - - // Advance name pointer by one because the current position didn't work - n++; - } -} diff --git a/lib/suppressions.h b/lib/suppressions.h index d6a186fb3..79bcb1c67 100644 --- a/lib/suppressions.h +++ b/lib/suppressions.h @@ -162,7 +162,6 @@ public: */ std::list getUnmatchedGlobalSuppressions(const bool unusedFunctionChecking) const; - static bool matchglob(const std::string &pattern, const std::string &name); private: /** @brief List of error which the user doesn't want to see. */ std::list mSuppressions; diff --git a/lib/utils.h b/lib/utils.h index 15cf940b1..e2fb965a6 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -25,6 +25,7 @@ #include #include #include +#include inline bool endsWith(const std::string &str, char c) { @@ -110,6 +111,87 @@ inline static int caseInsensitiveStringCompare(const std::string &lhs, const std return 0; } +inline static bool isValidGlobPattern(const std::string& pattern) +{ + for (std::string::const_iterator i = pattern.begin(); i != pattern.end(); ++i) { + if (*i == '*' || *i == '?') { + std::string::const_iterator j = i + 1; + if (j != pattern.end() && (*j == '*' || *j == '?')) { + return false; + } + } + } + return true; +} + +inline static bool matchglob(const std::string& pattern, const std::string& name) +{ + const char* p = pattern.c_str(); + const char* n = name.c_str(); + std::stack > backtrack; + + for (;;) { + bool matching = true; + while (*p != '\0' && matching) { + switch (*p) { + case '*': + // Step forward until we match the next character after * + while (*n != '\0' && *n != p[1]) { + n++; + } + if (*n != '\0') { + // If this isn't the last possibility, save it for later + backtrack.push(std::make_pair(p, n)); + } + break; + case '?': + // Any character matches unless we're at the end of the name + if (*n != '\0') { + n++; + } + else { + matching = false; + } + break; + default: + // Non-wildcard characters match literally + if (*n == *p) { + n++; + } + else if (*n == '\\' && *p == '/') { + n++; + } + else if (*n == '/' && *p == '\\') { + n++; + } + else { + matching = false; + } + break; + } + p++; + } + + // If we haven't failed matching and we've reached the end of the name, then success + if (matching && *n == '\0') { + return true; + } + + // If there are no other paths to try, then fail + if (backtrack.empty()) { + return false; + } + + // Restore pointers from backtrack stack + p = backtrack.top().first; + n = backtrack.top().second; + backtrack.pop(); + + // Advance name pointer by one because the current position didn't work + n++; + } +} + #define UNUSED(x) (void)(x) // Use the nonneg macro when you want to assert that a variable/argument is not negative diff --git a/test/testimportproject.cpp b/test/testimportproject.cpp index 2d1e4eab7..bcd8cce95 100644 --- a/test/testimportproject.cpp +++ b/test/testimportproject.cpp @@ -50,6 +50,7 @@ private: TEST_CASE(importCompileCommandsArgumentsSection); // Handle arguments section TEST_CASE(importCompileCommandsNoCommandSection); // gracefully handles malformed json TEST_CASE(importCppcheckGuiProject); + TEST_CASE(ignorePaths); } void setDefines() const { @@ -185,6 +186,24 @@ private: ASSERT_EQUALS(1, s.includePaths.size()); ASSERT_EQUALS("lib/", s.includePaths.front()); } + + void ignorePaths() { + ImportProject::FileSettings fs1, fs2; + fs1.filename = "foo/bar"; + fs2.filename = "qwe/rty"; + TestImporter project; + project.fileSettings = {fs1, fs2}; + + project.ignorePaths({"*foo", "bar*"}); + ASSERT_EQUALS(2, project.fileSettings.size()); + + project.ignorePaths({"foo/*"}); + ASSERT_EQUALS(1, project.fileSettings.size()); + ASSERT_EQUALS("qwe/rty", project.fileSettings.front().filename); + + project.ignorePaths({ "*e/r*" }); + ASSERT_EQUALS(0, project.fileSettings.size()); + } }; REGISTER_TEST(TestImportProject) diff --git a/test/testrunner.vcxproj b/test/testrunner.vcxproj index d37a61504..72eda6e43 100755 --- a/test/testrunner.vcxproj +++ b/test/testrunner.vcxproj @@ -92,6 +92,7 @@ + diff --git a/test/testrunner.vcxproj.filters b/test/testrunner.vcxproj.filters index 9c413e6e9..dd8974e04 100644 --- a/test/testrunner.vcxproj.filters +++ b/test/testrunner.vcxproj.filters @@ -217,6 +217,9 @@ Source Files + + Source Files + diff --git a/test/testsuppressions.cpp b/test/testsuppressions.cpp index 43763daf3..e94c845c6 100644 --- a/test/testsuppressions.cpp +++ b/test/testsuppressions.cpp @@ -66,8 +66,6 @@ private: TEST_CASE(unusedFunction); - TEST_CASE(matchglob); - TEST_CASE(suppressingSyntaxErrorAndExitCode); } @@ -590,23 +588,6 @@ private: ASSERT_EQUALS(0, checkSuppression("void f() {}", "unusedFunction")); } - void matchglob() { - ASSERT_EQUALS(true, Suppressions::matchglob("*", "xyz")); - ASSERT_EQUALS(true, Suppressions::matchglob("x*", "xyz")); - ASSERT_EQUALS(true, Suppressions::matchglob("*z", "xyz")); - ASSERT_EQUALS(true, Suppressions::matchglob("*y*", "xyz")); - ASSERT_EQUALS(true, Suppressions::matchglob("*y*", "yz")); - ASSERT_EQUALS(false, Suppressions::matchglob("*y*", "abc")); - ASSERT_EQUALS(true, Suppressions::matchglob("*", "x/y/z")); - ASSERT_EQUALS(true, Suppressions::matchglob("*/y/z", "x/y/z")); - - ASSERT_EQUALS(false, Suppressions::matchglob("?", "xyz")); - ASSERT_EQUALS(false, Suppressions::matchglob("x?", "xyz")); - ASSERT_EQUALS(false, Suppressions::matchglob("?z", "xyz")); - ASSERT_EQUALS(true, Suppressions::matchglob("?y?", "xyz")); - ASSERT_EQUALS(true, Suppressions::matchglob("?/?/?", "x/y/z")); - } - void suppressingSyntaxErrorAndExitCode() { std::map files; files["test.cpp"] = "fi if;"; diff --git a/test/testutils.cpp b/test/testutils.cpp new file mode 100644 index 000000000..88d95a68f --- /dev/null +++ b/test/testutils.cpp @@ -0,0 +1,72 @@ +/* + * Cppcheck - A tool for static C/C++ code analysis + * Copyright (C) 2007-2019 Cppcheck team. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "testsuite.h" +#include "utils.h" + +class TestUtils : public TestFixture { +public: + TestUtils() : TestFixture("TestUtils") { + } + +private: + void run() OVERRIDE { + TEST_CASE(isValidGlobPattern); + TEST_CASE(matchglob); + } + + void isValidGlobPattern() { + ASSERT_EQUALS(true, ::isValidGlobPattern("*")); + ASSERT_EQUALS(true, ::isValidGlobPattern("*x")); + ASSERT_EQUALS(true, ::isValidGlobPattern("x*")); + ASSERT_EQUALS(true, ::isValidGlobPattern("*/x/*")); + ASSERT_EQUALS(true, ::isValidGlobPattern("x/*/z")); + ASSERT_EQUALS(false, ::isValidGlobPattern("**")); + ASSERT_EQUALS(false, ::isValidGlobPattern("**x")); + ASSERT_EQUALS(false, ::isValidGlobPattern("x**")); + + ASSERT_EQUALS(true, ::isValidGlobPattern("?")); + ASSERT_EQUALS(true, ::isValidGlobPattern("?x")); + ASSERT_EQUALS(true, ::isValidGlobPattern("x?")); + ASSERT_EQUALS(true, ::isValidGlobPattern("?/x/?")); + ASSERT_EQUALS(true, ::isValidGlobPattern("x/?/z")); + ASSERT_EQUALS(false, ::isValidGlobPattern("??")); + ASSERT_EQUALS(false, ::isValidGlobPattern("??x")); + ASSERT_EQUALS(false, ::isValidGlobPattern("x??")); + } + + void matchglob() { + ASSERT_EQUALS(true, ::matchglob("*", "xyz")); + ASSERT_EQUALS(true, ::matchglob("x*", "xyz")); + ASSERT_EQUALS(true, ::matchglob("*z", "xyz")); + ASSERT_EQUALS(true, ::matchglob("*y*", "xyz")); + ASSERT_EQUALS(true, ::matchglob("*y*", "yz")); + ASSERT_EQUALS(false, ::matchglob("*y*", "abc")); + ASSERT_EQUALS(true, ::matchglob("*", "x/y/z")); + ASSERT_EQUALS(true, ::matchglob("*/y/z", "x/y/z")); + + ASSERT_EQUALS(false, ::matchglob("?", "xyz")); + ASSERT_EQUALS(false, ::matchglob("x?", "xyz")); + ASSERT_EQUALS(false, ::matchglob("?z", "xyz")); + ASSERT_EQUALS(true, ::matchglob("?y?", "xyz")); + ASSERT_EQUALS(true, ::matchglob("?/?/?", "x/y/z")); + } +}; + +REGISTER_TEST(TestUtils)