Skip to content

Commit d756381

Browse files
committed
Add file mode specifier to pathmatch, more tests, cleanup
1 parent c8031b4 commit d756381

5 files changed

Lines changed: 99 additions & 43 deletions

File tree

cli/filelister.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ static std::string addFiles2(std::list<FileWithDetails>&files, const std::string
129129
} else {
130130
// Directory
131131
if (recursive) {
132-
if (!ignored.match(fname)) {
132+
if (!ignored.match(fname, PathMatch::Filemode::directory)) {
133133
std::list<FileWithDetails> filesSorted;
134134

135135
std::string err = addFiles2(filesSorted, fname, extra, recursive, ignored);
@@ -241,7 +241,7 @@ static std::string addFiles2(std::list<FileWithDetails> &files,
241241
#endif
242242
if (path_is_directory) {
243243
if (recursive) {
244-
if (!ignored.match(new_path)) {
244+
if (!ignored.match(new_path, PathMatch::Filemode::directory)) {
245245
std::string err = addFiles2(files, new_path, extra, recursive, ignored, debug);
246246
if (!err.empty()) {
247247
return err;

lib/pathmatch.cpp

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,44 @@ PathMatch::PathMatch(std::vector<std::string> patterns, std::string basepath, Sy
3030
mPatterns(std::move(patterns)), mBasepath(std::move(basepath)), mSyntax(syntax)
3131
{}
3232

33-
bool PathMatch::match(const std::string &path) const
33+
bool PathMatch::match(const std::string &path, Filemode mode) const
3434
{
3535
return std::any_of(mPatterns.cbegin(), mPatterns.cend(), [=] (const std::string &pattern) {
36-
return match(pattern, path, mBasepath, mSyntax);
36+
return match(pattern, path, mBasepath, mode, mSyntax);
3737
});
3838
}
3939

40-
bool PathMatch::match(const std::string &pattern, const std::string &path, const std::string &basepath, Syntax syntax)
40+
bool PathMatch::match(const std::string &pattern, const std::string &path, const std::string &basepath, Filemode mode, Syntax syntax)
4141
{
42+
/* Fast paths for common patterns */
4243
if (pattern.empty())
4344
return false;
4445

4546
if (pattern == "*" || pattern == "**")
4647
return true;
4748

48-
/* A "real" path is absolute or relative to the base path. A pattern that isn't "real" can match at any
49+
/* If the pattern ends with a path separator it matches only directories. If the path names a regular file then
50+
* the last path component can't match. */
51+
bool dir_mismatch = PathIterator::issep(pattern.back(), syntax) && mode != Filemode::directory;
52+
53+
if (!dir_mismatch && pattern == path)
54+
return true;
55+
56+
/* A "real" pattern is absolute or relative to the base path. A pattern that isn't real can match at any
4957
* path component boundary. */
5058
bool real = Path::isAbsolute(pattern) || isRelativePattern(pattern);
5159

5260
/* Pattern iterator */
5361
PathIterator s = PathIterator::fromPattern(pattern, basepath, syntax);
5462
/* Path iterator */
5563
PathIterator t = PathIterator::fromPath(path, basepath, syntax);
64+
65+
if (dir_mismatch) {
66+
/* Final compponent can't match, so skip it. */
67+
while (*t != '\0' && *t != '/')
68+
++t;
69+
}
70+
5671
/* Pattern restart position */
5772
PathIterator p = s;
5873
/* Path restart position */
@@ -72,8 +87,6 @@ bool PathMatch::match(const std::string &pattern, const std::string &path, const
7287
slash = true;
7388
++s;
7489
}
75-
/* Add backtrack for matching zero characters */
76-
b.emplace(s.getpos(), t.getpos());
7790
while (*t != '\0' && (slash || *t != '/')) {
7891
if (*s == *t) {
7992
/* Could stop here, but do greedy match and add

lib/pathmatch.h

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@
5454
* - Otherwise:
5555
* - Match all files where the pattern matches any part of the file's canonical absolute path up until a
5656
* path separator or the end of the pathname, and the matching part directly follows a path separator.
57+
* - If a pattern ends with a path separator before canonicalization then the final path component of the pattern
58+
* only matches the final path component of the file path if the file is a directory (specified by the file mode
59+
* parameter).
5760
*
58-
* TODO: Handle less common windows windows syntaxes:
61+
* TODO: Handle less common windows syntaxes:
5962
* - Drive-specific relative path: C:dir\foo.cpp
6063
* - Root-relative path: \dir\foo.cpp
6164
**/
@@ -87,6 +90,17 @@ class CPPCHECKLIB PathMatch {
8790
static constexpr Syntax platform_syntax = Syntax::unix;
8891
#endif
8992

93+
/**
94+
* @brief File mode of a file being matched
95+
*
96+
* regular: File is a regular file.
97+
* directory: File is a directory.
98+
*/
99+
enum class Filemode : std::uint8_t {
100+
regular,
101+
directory,
102+
};
103+
90104
/**
91105
* The constructor.
92106
*
@@ -100,20 +114,22 @@ class CPPCHECKLIB PathMatch {
100114
* @brief Match path against list of patterns.
101115
*
102116
* @param path Path to match.
117+
* @param mode The file mode of the file named by the path.
103118
* @return true if any of the masks match the path, false otherwise.
104119
*/
105-
bool match(const std::string &path) const;
120+
bool match(const std::string &path, Filemode mode = Filemode::regular) const;
106121

107122
/**
108123
* @brief Match path against a single pattern.
109124
*
110125
* @param pattern Pattern to use.
111126
* @param path Path to match.
112127
* @param basepath Path to which the pattern and path is relative, when applicable.
128+
* @param mode The file mode of the file named by the path.
113129
* @param syntax Path syntax.
114130
* @return true if the pattern matches the path, false otherwise.
115131
*/
116-
static bool match(const std::string &pattern, const std::string &path, const std::string &basepath = std::string(), Syntax syntax = platform_syntax);
132+
static bool match(const std::string &pattern, const std::string &path, const std::string &basepath = std::string(), Filemode mode = Filemode::regular, Syntax syntax = platform_syntax);
117133

118134
/**
119135
* @brief Check if a pattern is a relative path name.
@@ -209,12 +225,6 @@ class PathMatch::PathIterator {
209225
explicit PathIterator(const char *path_a = nullptr, const char *path_b = nullptr, Syntax syntax = platform_syntax) :
210226
mStart{path_a, path_b}, mSyntax(syntax)
211227
{
212-
const auto issep = [syntax] (char c) {
213-
return c == '/' || (syntax == Syntax::windows && c == '\\');
214-
};
215-
const auto isdrive = [] (char c) {
216-
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
217-
};
218228

219229
for (int i = 0; i < 2; i++) {
220230
const char *&p = mEnd[i];
@@ -225,19 +235,19 @@ class PathMatch::PathIterator {
225235

226236
if (mPos.l == 0) {
227237
/* Check length of root component */
228-
if (issep(p[0])) {
238+
if (issep(p[0], syntax)) {
229239
mRootLength++;
230-
if (syntax == Syntax::windows && issep(p[1])) {
240+
if (syntax == Syntax::windows && issep(p[1], syntax)) {
231241
mRootLength++;
232242
if (p[2] == '.' || p[2] == '?') {
233243
mRootLength++;
234-
if (issep(p[3]))
244+
if (issep(p[3], syntax))
235245
mRootLength++;
236246
}
237247
}
238248
} else if (syntax == Syntax::windows && isdrive(p[0]) && p[1] == ':') {
239249
mRootLength += 2;
240-
if (issep(p[2]))
250+
if (issep(p[2], syntax))
241251
mRootLength++;
242252
}
243253
p += mRootLength;
@@ -308,6 +318,18 @@ class PathMatch::PathIterator {
308318
return str;
309319
}
310320

321+
/* Syntax helper, check if a character is a path separator */
322+
static bool issep(char c, Syntax syntax)
323+
{
324+
return c == '/' || (syntax == Syntax::windows && c == '\\');
325+
}
326+
327+
/* Syntax helper, check if a chracter is a drive letter */
328+
static bool isdrive(char c)
329+
{
330+
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
331+
}
332+
311333
private:
312334
/* Read the current character */
313335
char current() const

lib/suppressions.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ SuppressionList::Suppression::Result SuppressionList::Suppression::isSuppressed(
401401
if (!thisAndNextLine || lineNumber + 1 != errmsg.lineNumber)
402402
return Result::None;
403403
}
404-
if (!fileName.empty() && fileName != errmsg.getFileName() && !PathMatch::match(fileName, errmsg.getFileName()))
404+
if (!fileName.empty() && !PathMatch::match(fileName, errmsg.getFileName()))
405405
return Result::None;
406406
if (hash > 0 && hash != errmsg.hash)
407407
return Result::Checked;

test/testpathmatch.cpp

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class TestPathMatch : public TestFixture {
3030
private:
3131
static constexpr auto unix = PathMatch::Syntax::unix;
3232
static constexpr auto windows = PathMatch::Syntax::windows;
33+
static constexpr auto ifreg = PathMatch::Filemode::regular;
34+
static constexpr auto ifdir = PathMatch::Filemode::directory;
3335
#ifdef _WIN32
3436
const std::string basepath{"C:\\test"};
3537
#else
@@ -73,7 +75,8 @@ class TestPathMatch : public TestFixture {
7375
TEST_CASE(filemaskpath3);
7476
TEST_CASE(filemaskpath4);
7577
TEST_CASE(mixedallmatch);
76-
TEST_CASE(glob);
78+
TEST_CASE(glob1);
79+
TEST_CASE(glob2);
7780
TEST_CASE(globstar1);
7881
TEST_CASE(globstar2);
7982
TEST_CASE(pathiterator);
@@ -85,16 +88,16 @@ class TestPathMatch : public TestFixture {
8588
}
8689

8790
void emptymaskpath1() const {
88-
ASSERT(!emptyMatcher.match("src/"));
91+
ASSERT(!emptyMatcher.match("src/", ifdir));
8992
}
9093

9194
void emptymaskpath2() const {
92-
ASSERT(!emptyMatcher.match("../src/"));
95+
ASSERT(!emptyMatcher.match("../src/", ifdir));
9396
}
9497

9598
void emptymaskpath3() const {
96-
ASSERT(!emptyMatcher.match("/home/user/code/src/"));
97-
ASSERT(!emptyMatcher.match("d:/home/user/code/src/"));
99+
ASSERT(!emptyMatcher.match("/home/user/code/src/", ifdir));
100+
ASSERT(!emptyMatcher.match("d:/home/user/code/src/", ifdir));
98101
}
99102

100103
// Test PathMatch containing "src/"
@@ -103,17 +106,20 @@ class TestPathMatch : public TestFixture {
103106
}
104107

105108
void onemasksamepath() const {
106-
ASSERT(srcMatcher.match("src/"));
109+
ASSERT(srcMatcher.match("src/", ifdir));
110+
ASSERT(!srcMatcher.match("src/", ifreg));
107111
}
108112

109113
void onemasksamepathdifferentslash() const {
110114
PathMatch srcMatcher2({"src\\"}, basepath, windows);
111-
ASSERT(srcMatcher2.match("src/"));
115+
ASSERT(srcMatcher2.match("src/", ifdir));
116+
ASSERT(!srcMatcher2.match("src/", ifreg));
112117
}
113118

114119
void onemasksamepathdifferentcase() const {
115120
PathMatch match({"sRc/"}, basepath, windows);
116-
ASSERT(match.match("srC/"));
121+
ASSERT(match.match("srC/", ifdir));
122+
ASSERT(!match.match("srC/", ifreg));
117123
}
118124

119125
void onemasksamepathwithfile() const {
@@ -125,8 +131,8 @@ class TestPathMatch : public TestFixture {
125131
const std::string shorterToMatch("src/");
126132
ASSERT(shorterToMatch.length() < longerExclude.length());
127133
PathMatch match({longerExclude});
128-
ASSERT(match.match(longerExclude));
129-
ASSERT(!match.match(shorterToMatch));
134+
ASSERT(match.match(longerExclude, ifdir));
135+
ASSERT(!match.match(shorterToMatch, ifdir));
130136
}
131137

132138
void onemaskdifferentdir1() const {
@@ -146,40 +152,43 @@ class TestPathMatch : public TestFixture {
146152
}
147153

148154
void onemasklongerpath1() const {
149-
ASSERT(srcMatcher.match("/tmp/src/"));
150-
ASSERT(srcMatcher.match("d:/tmp/src/"));
155+
ASSERT(srcMatcher.match("/tmp/src/", ifdir));
156+
ASSERT(srcMatcher.match("d:/tmp/src/", ifdir));
151157
}
152158

153159
void onemasklongerpath2() const {
154-
ASSERT(srcMatcher.match("src/module/"));
160+
ASSERT(srcMatcher.match("src/module/", ifdir));
155161
}
156162

157163
void onemasklongerpath3() const {
158-
ASSERT(srcMatcher.match("project/src/module/"));
164+
ASSERT(srcMatcher.match("project/src/module/", ifdir));
159165
}
160166

161167
void onemaskcwd() const {
162-
ASSERT(srcMatcher.match("./src"));
168+
ASSERT(srcMatcher.match("./src", ifdir));
163169
}
164170

165171
void twomasklongerpath1() const {
166172
PathMatch match({ "src/", "module/" });
167-
ASSERT(!match.match("project/"));
173+
ASSERT(!match.match("project/", ifdir));
168174
}
169175

170176
void twomasklongerpath2() const {
171177
PathMatch match({ "src/", "module/" });
172-
ASSERT(match.match("project/src/"));
178+
ASSERT(match.match("project/src/", ifdir));
179+
ASSERT(!match.match("project/src/", ifreg));
173180
}
174181

175182
void twomasklongerpath3() const {
176183
PathMatch match({ "src/", "module/" });
177-
ASSERT(match.match("project/module/"));
184+
ASSERT(match.match("project/module/", ifdir));
185+
ASSERT(!match.match("project/module/", ifreg));
178186
}
179187

180188
void twomasklongerpath4() const {
181189
PathMatch match({ "src/", "module/" });
182-
ASSERT(match.match("project/src/module/"));
190+
ASSERT(match.match("project/src/module/", ifdir));
191+
ASSERT(match.match("project/src/module/", ifreg));
183192
}
184193

185194
// Test PathMatch containing "foo.cpp"
@@ -224,11 +233,12 @@ class TestPathMatch : public TestFixture {
224233
void mixedallmatch() const { // #13570
225234
// when trying to match a directory against a directory entry it erroneously modified a local variable also used for file matching
226235
PathMatch match({ "tests/", "file.c" });
227-
ASSERT(match.match("tests/"));
236+
ASSERT(match.match("tests/", ifdir));
237+
ASSERT(!match.match("tests/", ifreg));
228238
ASSERT(match.match("lib/file.c"));
229239
}
230240

231-
void glob() const {
241+
void glob1() const {
232242
PathMatch match({"test?.cpp"});
233243
ASSERT(match.match("test1.cpp"));
234244
ASSERT(match.match("src/test1.cpp"));
@@ -237,12 +247,22 @@ class TestPathMatch : public TestFixture {
237247
ASSERT(!match.match("test.cpp"));
238248
}
239249

250+
void glob2() const {
251+
PathMatch match({"test*.cpp"});
252+
ASSERT(match.match("test1.cpp"));
253+
ASSERT(match.match("src/test1.cpp"));
254+
ASSERT(match.match("test1.cpp/src"));
255+
ASSERT(!match.match("test1.c"));
256+
ASSERT(match.match("test.cpp"));
257+
}
258+
240259
void globstar1() const {
241260
PathMatch match({"src/**/foo.c"});
242261
ASSERT(match.match("src/lib/foo/foo.c"));
243262
ASSERT(match.match("src/lib/foo/bar/foo.c"));
244263
ASSERT(!match.match("src/lib/foo/foo.cpp"));
245264
ASSERT(!match.match("src/lib/foo/bar/foo.cpp"));
265+
ASSERT(!match.match("src/foo.c"));
246266
}
247267

248268
void globstar2() const {
@@ -251,6 +271,7 @@ class TestPathMatch : public TestFixture {
251271
ASSERT(match.match("src/lib/foo/bar/foo.c"));
252272
ASSERT(!match.match("src/lib/foo/foo.cpp"));
253273
ASSERT(!match.match("src/lib/foo/bar/foo.cpp"));
274+
ASSERT(!match.match("src/foo.c"));
254275
}
255276

256277
void pathiterator() const {

0 commit comments

Comments
 (0)