Skip to content

Commit 11993ed

Browse files
aleserbdanmar
authored andcommitted
Ticket 5607: Allow to exclude folders with glob pattern (cppcheck-opensource#2498)
1 parent e6670fe commit 11993ed

10 files changed

Lines changed: 184 additions & 100 deletions

cli/cppcheckexecutor.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ bool CppCheckExecutor::parseFromArgs(CppCheck *cppcheck, int argc, const char* c
164164
std::list<ImportProject::FileSettings> newList;
165165

166166
for (const ImportProject::FileSettings &fsetting : settings.project.fileSettings) {
167-
if (Suppressions::matchglob(mSettings->fileFilter, fsetting.filename)) {
167+
if (matchglob(mSettings->fileFilter, fsetting.filename)) {
168168
newList.push_back(fsetting);
169169
}
170170
}
@@ -189,7 +189,7 @@ bool CppCheckExecutor::parseFromArgs(CppCheck *cppcheck, int argc, const char* c
189189
} else if (!mSettings->fileFilter.empty()) {
190190
std::map<std::string, std::size_t> newMap;
191191
for (std::map<std::string, std::size_t>::const_iterator i = mFiles.begin(); i != mFiles.end(); ++i)
192-
if (Suppressions::matchglob(mSettings->fileFilter, i->first)) {
192+
if (matchglob(mSettings->fileFilter, i->first)) {
193193
newMap[i->first] = i->second;
194194
}
195195
mFiles = newMap;

lib/importproject.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ void ImportProject::ignorePaths(const std::vector<std::string> &ipaths)
4343
ignore = true;
4444
break;
4545
}
46+
if (isValidGlobPattern(i) && matchglob(i, it->filename)) {
47+
ignore = true;
48+
break;
49+
}
4650
if (!Path::isAbsolute(i)) {
4751
i = mPath + i;
4852
if (it->filename.size() > i.size() && it->filename.compare(0,i.size(),i)==0) {

lib/suppressions.cpp

Lines changed: 1 addition & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,17 @@
2121
#include "errorlogger.h"
2222
#include "mathlib.h"
2323
#include "path.h"
24+
#include "utils.h"
2425

2526
#include <tinyxml2.h>
2627

2728
#include <algorithm>
2829
#include <cctype> // std::isdigit, std::isalnum, etc
29-
#include <stack>
3030
#include <sstream>
3131
#include <utility>
3232

3333
class ErrorLogger;
3434

35-
static bool isValidGlobPattern(const std::string &pattern)
36-
{
37-
for (std::string::const_iterator i = pattern.begin(); i != pattern.end(); ++i) {
38-
if (*i == '*' || *i == '?') {
39-
std::string::const_iterator j = i + 1;
40-
if (j != pattern.end() && (*j == '*' || *j == '?')) {
41-
return false;
42-
}
43-
}
44-
}
45-
return true;
46-
}
47-
4835
static bool isAcceptedErrorIdChar(char c)
4936
{
5037
switch (c) {
@@ -354,67 +341,3 @@ std::list<Suppressions::Suppression> Suppressions::getUnmatchedGlobalSuppression
354341
}
355342
return result;
356343
}
357-
358-
bool Suppressions::matchglob(const std::string &pattern, const std::string &name)
359-
{
360-
const char *p = pattern.c_str();
361-
const char *n = name.c_str();
362-
std::stack<std::pair<const char *, const char *> > backtrack;
363-
364-
for (;;) {
365-
bool matching = true;
366-
while (*p != '\0' && matching) {
367-
switch (*p) {
368-
case '*':
369-
// Step forward until we match the next character after *
370-
while (*n != '\0' && *n != p[1]) {
371-
n++;
372-
}
373-
if (*n != '\0') {
374-
// If this isn't the last possibility, save it for later
375-
backtrack.push(std::make_pair(p, n));
376-
}
377-
break;
378-
case '?':
379-
// Any character matches unless we're at the end of the name
380-
if (*n != '\0') {
381-
n++;
382-
} else {
383-
matching = false;
384-
}
385-
break;
386-
default:
387-
// Non-wildcard characters match literally
388-
if (*n == *p) {
389-
n++;
390-
} else if (*n == '\\' && *p == '/') {
391-
n++;
392-
} else if (*n == '/' && *p == '\\') {
393-
n++;
394-
} else {
395-
matching = false;
396-
}
397-
break;
398-
}
399-
p++;
400-
}
401-
402-
// If we haven't failed matching and we've reached the end of the name, then success
403-
if (matching && *n == '\0') {
404-
return true;
405-
}
406-
407-
// If there are no other paths to try, then fail
408-
if (backtrack.empty()) {
409-
return false;
410-
}
411-
412-
// Restore pointers from backtrack stack
413-
p = backtrack.top().first;
414-
n = backtrack.top().second;
415-
backtrack.pop();
416-
417-
// Advance name pointer by one because the current position didn't work
418-
n++;
419-
}
420-
}

lib/suppressions.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ class CPPCHECKLIB Suppressions {
162162
*/
163163
std::list<Suppression> getUnmatchedGlobalSuppressions(const bool unusedFunctionChecking) const;
164164

165-
static bool matchglob(const std::string &pattern, const std::string &name);
166165
private:
167166
/** @brief List of error which the user doesn't want to see. */
168167
std::list<Suppression> mSuppressions;

lib/utils.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <cctype>
2626
#include <cstddef>
2727
#include <string>
28+
#include <stack>
2829

2930
inline bool endsWith(const std::string &str, char c)
3031
{
@@ -110,6 +111,87 @@ inline static int caseInsensitiveStringCompare(const std::string &lhs, const std
110111
return 0;
111112
}
112113

114+
inline static bool isValidGlobPattern(const std::string& pattern)
115+
{
116+
for (std::string::const_iterator i = pattern.begin(); i != pattern.end(); ++i) {
117+
if (*i == '*' || *i == '?') {
118+
std::string::const_iterator j = i + 1;
119+
if (j != pattern.end() && (*j == '*' || *j == '?')) {
120+
return false;
121+
}
122+
}
123+
}
124+
return true;
125+
}
126+
127+
inline static bool matchglob(const std::string& pattern, const std::string& name)
128+
{
129+
const char* p = pattern.c_str();
130+
const char* n = name.c_str();
131+
std::stack<std::pair<const char*, const char*> > backtrack;
132+
133+
for (;;) {
134+
bool matching = true;
135+
while (*p != '\0' && matching) {
136+
switch (*p) {
137+
case '*':
138+
// Step forward until we match the next character after *
139+
while (*n != '\0' && *n != p[1]) {
140+
n++;
141+
}
142+
if (*n != '\0') {
143+
// If this isn't the last possibility, save it for later
144+
backtrack.push(std::make_pair(p, n));
145+
}
146+
break;
147+
case '?':
148+
// Any character matches unless we're at the end of the name
149+
if (*n != '\0') {
150+
n++;
151+
}
152+
else {
153+
matching = false;
154+
}
155+
break;
156+
default:
157+
// Non-wildcard characters match literally
158+
if (*n == *p) {
159+
n++;
160+
}
161+
else if (*n == '\\' && *p == '/') {
162+
n++;
163+
}
164+
else if (*n == '/' && *p == '\\') {
165+
n++;
166+
}
167+
else {
168+
matching = false;
169+
}
170+
break;
171+
}
172+
p++;
173+
}
174+
175+
// If we haven't failed matching and we've reached the end of the name, then success
176+
if (matching && *n == '\0') {
177+
return true;
178+
}
179+
180+
// If there are no other paths to try, then fail
181+
if (backtrack.empty()) {
182+
return false;
183+
}
184+
185+
// Restore pointers from backtrack stack
186+
p = backtrack.top().first;
187+
n = backtrack.top().second;
188+
backtrack.pop();
189+
190+
// Advance name pointer by one because the current position didn't work
191+
n++;
192+
}
193+
}
194+
113195
#define UNUSED(x) (void)(x)
114196

115197
// Use the nonneg macro when you want to assert that a variable/argument is not negative

test/testimportproject.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class TestImportProject : public TestFixture {
5050
TEST_CASE(importCompileCommandsArgumentsSection); // Handle arguments section
5151
TEST_CASE(importCompileCommandsNoCommandSection); // gracefully handles malformed json
5252
TEST_CASE(importCppcheckGuiProject);
53+
TEST_CASE(ignorePaths);
5354
}
5455

5556
void setDefines() const {
@@ -185,6 +186,24 @@ class TestImportProject : public TestFixture {
185186
ASSERT_EQUALS(1, s.includePaths.size());
186187
ASSERT_EQUALS("lib/", s.includePaths.front());
187188
}
189+
190+
void ignorePaths() {
191+
ImportProject::FileSettings fs1, fs2;
192+
fs1.filename = "foo/bar";
193+
fs2.filename = "qwe/rty";
194+
TestImporter project;
195+
project.fileSettings = {fs1, fs2};
196+
197+
project.ignorePaths({"*foo", "bar*"});
198+
ASSERT_EQUALS(2, project.fileSettings.size());
199+
200+
project.ignorePaths({"foo/*"});
201+
ASSERT_EQUALS(1, project.fileSettings.size());
202+
ASSERT_EQUALS("qwe/rty", project.fileSettings.front().filename);
203+
204+
project.ignorePaths({ "*e/r*" });
205+
ASSERT_EQUALS(0, project.fileSettings.size());
206+
}
188207
};
189208

190209
REGISTER_TEST(TestImportProject)

test/testrunner.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
<ClCompile Include="testunusedfunctions.cpp" />
9393
<ClCompile Include="testunusedprivfunc.cpp" />
9494
<ClCompile Include="testunusedvar.cpp" />
95+
<ClCompile Include="testutils.cpp" />
9596
<ClCompile Include="testvaarg.cpp" />
9697
<ClCompile Include="testvalueflow.cpp" />
9798
<ClCompile Include="testvarid.cpp" />

test/testrunner.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@
217217
<ClCompile Include="testexprengine.cpp">
218218
<Filter>Source Files</Filter>
219219
</ClCompile>
220+
<ClCompile Include="testutils.cpp">
221+
<Filter>Source Files</Filter>
222+
</ClCompile>
220223
</ItemGroup>
221224
<ItemGroup>
222225
<ClInclude Include="options.h">

test/testsuppressions.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,6 @@ class TestSuppressions : public TestFixture {
6666

6767
TEST_CASE(unusedFunction);
6868

69-
TEST_CASE(matchglob);
70-
7169
TEST_CASE(suppressingSyntaxErrorAndExitCode);
7270
}
7371

@@ -590,23 +588,6 @@ class TestSuppressions : public TestFixture {
590588
ASSERT_EQUALS(0, checkSuppression("void f() {}", "unusedFunction"));
591589
}
592590

593-
void matchglob() {
594-
ASSERT_EQUALS(true, Suppressions::matchglob("*", "xyz"));
595-
ASSERT_EQUALS(true, Suppressions::matchglob("x*", "xyz"));
596-
ASSERT_EQUALS(true, Suppressions::matchglob("*z", "xyz"));
597-
ASSERT_EQUALS(true, Suppressions::matchglob("*y*", "xyz"));
598-
ASSERT_EQUALS(true, Suppressions::matchglob("*y*", "yz"));
599-
ASSERT_EQUALS(false, Suppressions::matchglob("*y*", "abc"));
600-
ASSERT_EQUALS(true, Suppressions::matchglob("*", "x/y/z"));
601-
ASSERT_EQUALS(true, Suppressions::matchglob("*/y/z", "x/y/z"));
602-
603-
ASSERT_EQUALS(false, Suppressions::matchglob("?", "xyz"));
604-
ASSERT_EQUALS(false, Suppressions::matchglob("x?", "xyz"));
605-
ASSERT_EQUALS(false, Suppressions::matchglob("?z", "xyz"));
606-
ASSERT_EQUALS(true, Suppressions::matchglob("?y?", "xyz"));
607-
ASSERT_EQUALS(true, Suppressions::matchglob("?/?/?", "x/y/z"));
608-
}
609-
610591
void suppressingSyntaxErrorAndExitCode() {
611592
std::map<std::string, std::string> files;
612593
files["test.cpp"] = "fi if;";

test/testutils.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Cppcheck - A tool for static C/C++ code analysis
3+
* Copyright (C) 2007-2019 Cppcheck team.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
20+
#include "testsuite.h"
21+
#include "utils.h"
22+
23+
class TestUtils : public TestFixture {
24+
public:
25+
TestUtils() : TestFixture("TestUtils") {
26+
}
27+
28+
private:
29+
void run() OVERRIDE {
30+
TEST_CASE(isValidGlobPattern);
31+
TEST_CASE(matchglob);
32+
}
33+
34+
void isValidGlobPattern() {
35+
ASSERT_EQUALS(true, ::isValidGlobPattern("*"));
36+
ASSERT_EQUALS(true, ::isValidGlobPattern("*x"));
37+
ASSERT_EQUALS(true, ::isValidGlobPattern("x*"));
38+
ASSERT_EQUALS(true, ::isValidGlobPattern("*/x/*"));
39+
ASSERT_EQUALS(true, ::isValidGlobPattern("x/*/z"));
40+
ASSERT_EQUALS(false, ::isValidGlobPattern("**"));
41+
ASSERT_EQUALS(false, ::isValidGlobPattern("**x"));
42+
ASSERT_EQUALS(false, ::isValidGlobPattern("x**"));
43+
44+
ASSERT_EQUALS(true, ::isValidGlobPattern("?"));
45+
ASSERT_EQUALS(true, ::isValidGlobPattern("?x"));
46+
ASSERT_EQUALS(true, ::isValidGlobPattern("x?"));
47+
ASSERT_EQUALS(true, ::isValidGlobPattern("?/x/?"));
48+
ASSERT_EQUALS(true, ::isValidGlobPattern("x/?/z"));
49+
ASSERT_EQUALS(false, ::isValidGlobPattern("??"));
50+
ASSERT_EQUALS(false, ::isValidGlobPattern("??x"));
51+
ASSERT_EQUALS(false, ::isValidGlobPattern("x??"));
52+
}
53+
54+
void matchglob() {
55+
ASSERT_EQUALS(true, ::matchglob("*", "xyz"));
56+
ASSERT_EQUALS(true, ::matchglob("x*", "xyz"));
57+
ASSERT_EQUALS(true, ::matchglob("*z", "xyz"));
58+
ASSERT_EQUALS(true, ::matchglob("*y*", "xyz"));
59+
ASSERT_EQUALS(true, ::matchglob("*y*", "yz"));
60+
ASSERT_EQUALS(false, ::matchglob("*y*", "abc"));
61+
ASSERT_EQUALS(true, ::matchglob("*", "x/y/z"));
62+
ASSERT_EQUALS(true, ::matchglob("*/y/z", "x/y/z"));
63+
64+
ASSERT_EQUALS(false, ::matchglob("?", "xyz"));
65+
ASSERT_EQUALS(false, ::matchglob("x?", "xyz"));
66+
ASSERT_EQUALS(false, ::matchglob("?z", "xyz"));
67+
ASSERT_EQUALS(true, ::matchglob("?y?", "xyz"));
68+
ASSERT_EQUALS(true, ::matchglob("?/?/?", "x/y/z"));
69+
}
70+
};
71+
72+
REGISTER_TEST(TestUtils)

0 commit comments

Comments
 (0)