Skip to content

Commit 648c23a

Browse files
committed
Cleanup, fixes, more tests
1 parent 0c7d924 commit 648c23a

7 files changed

Lines changed: 146 additions & 97 deletions

File tree

cli/cmdlineparser.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,12 @@ CmdLineParser::Result CmdLineParser::parseFromArgs(int argc, const char* const a
16131613
return Result::Fail;
16141614
}
16151615

1616+
for (auto& path : mIgnoredPaths)
1617+
{
1618+
path = Path::removeQuotationMarks(std::move(path));
1619+
path = Path::fromNativeSeparators(std::move(path));
1620+
}
1621+
16161622
if (!project.guiProject.pathNames.empty())
16171623
mPathNames = project.guiProject.pathNames;
16181624

@@ -2138,7 +2144,7 @@ std::list<FileWithDetails> CmdLineParser::filterFiles(const std::vector<std::str
21382144
std::list<FileWithDetails> files;
21392145
PathMatch filtermatcher(fileFilters, Path::getCurrentPath());
21402146
std::copy_if(filesResolved.cbegin(), filesResolved.cend(), std::inserter(files, files.end()), [&](const FileWithDetails& entry) {
2141-
return filtermatcher.match(entry.path()) || filtermatcher.match(entry.spath());
2147+
return filtermatcher.match(entry.path());
21422148
});
21432149
return files;
21442150
}

cli/filelister.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,7 @@ static std::string addFiles2(std::list<FileWithDetails>&files, const std::string
129129
} else {
130130
// Directory
131131
if (recursive) {
132-
// append a slash if it is a directory since that is what we are doing for mIgnoredPaths directory entries.
133-
// otherwise we would ignore all its contents individually instead as a whole.
134-
if (!ignored.match(fname + '/')) {
132+
if (!ignored.match(fname)) {
135133
std::list<FileWithDetails> filesSorted;
136134

137135
std::string err = addFiles2(filesSorted, fname, extra, recursive, ignored);
@@ -243,9 +241,7 @@ static std::string addFiles2(std::list<FileWithDetails> &files,
243241
#endif
244242
if (path_is_directory) {
245243
if (recursive) {
246-
// append a slash if it is a directory since that is what we are doing for mIgnoredPaths directory entries.
247-
// otherwise we would ignore all its contents individually instead as a whole.
248-
if (!ignored.match(new_path + '/')) {
244+
if (!ignored.match(new_path)) {
249245
std::string err = addFiles2(files, new_path, extra, recursive, ignored, debug);
250246
if (!err.empty()) {
251247
return err;

lib/path.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -173,24 +173,19 @@ std::string Path::getCurrentExecutablePath(const char* fallback)
173173
return success ? std::string(buf) : std::string(fallback);
174174
}
175175

176-
static bool issep(char c)
177-
{
178-
return c == '/' || c == '\\';
179-
}
180-
181176
bool Path::isAbsolute(const std::string& path)
182177
{
183178
#ifdef _WIN32
184179
if (path.length() < 2)
185180
return false;
186181

187-
if (issep(path[0]) && issep(path[1]))
182+
if ((path[0] == '\\' || path[0] == '/') && (path[1] == '\\' || path[1] == '/'))
188183
return true;
189184

190185
// On Windows, 'C:\foo\bar' is an absolute path, while 'C:foo\bar' is not
191-
return std::isalpha(path[0]) && path[1] == ':' && issep(path[2]);
186+
return std::isalpha(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/');
192187
#else
193-
return !path.empty() && issep(path[0]);
188+
return !path.empty() && path[0] == '/';
194189
#endif
195190
}
196191

lib/pathmatch.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,18 @@
2626
#include <vector>
2727

2828

29-
PathMatch::PathMatch(std::vector<std::string> patterns, std::string basepath, Mode mode) :
30-
mPatterns(std::move(patterns)), mBasepath(std::move(basepath)), mMode(mode)
29+
PathMatch::PathMatch(std::vector<std::string> patterns, std::string basepath, Syntax syntax) :
30+
mPatterns(std::move(patterns)), mBasepath(std::move(basepath)), mSyntax(syntax)
3131
{}
3232

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

40-
bool PathMatch::match(const std::string &pattern, const std::string &path, const std::string &basepath, Mode mode)
40+
bool PathMatch::match(const std::string &pattern, const std::string &path, const std::string &basepath, Syntax syntax)
4141
{
4242
if (pattern.empty())
4343
return false;
@@ -50,9 +50,9 @@ bool PathMatch::match(const std::string &pattern, const std::string &path, const
5050
bool real = Path::isAbsolute(pattern) || isRelativePattern(pattern);
5151

5252
/* Pattern iterator */
53-
PathIterator s = PathIterator::fromPattern(pattern, basepath, mode == Mode::icase);
53+
PathIterator s = PathIterator::fromPattern(pattern, basepath, syntax);
5454
/* Path iterator */
55-
PathIterator t = PathIterator::fromPath(path, basepath, mode == Mode::icase);
55+
PathIterator t = PathIterator::fromPath(path, basepath, syntax);
5656
/* Pattern restart position */
5757
PathIterator p = s;
5858
/* Path restart position */

lib/pathmatch.h

Lines changed: 95 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -33,63 +33,68 @@
3333
/// @{
3434

3535
/**
36-
* Path matching rules:
37-
* - All patterns are canonicalized (path separators vary by platform):
38-
* - '/./' => '/'
39-
* - '/dir/../' => '/'
40-
* - '//' => '/'
41-
* - Trailing slashes are removed (root slash is preserved)
42-
* - Patterns can contain globs:
43-
* - '**' matches any number of characters including path separators.
44-
* - '*' matches any number of characters except path separators.
45-
* - '?' matches any single character except path separators.
46-
* - If a pattern looks like an absolute path (e.g. starts with '/', but varies by platform):
47-
* - Match all files where the pattern matches the start of the file's canonical absolute path up until a path
48-
* separator or the end of the pathname.
49-
* - If a pattern looks like a relative path, i.e. is '.' or '..', or
50-
* starts with '.' or '..' followed by a path separator:
51-
* - The pattern is interpreted as a path relative to `basepath` and then converted to an absolute path and
52-
* treated as such according to the above procedure. If the pattern is relative to some other directory, it should
53-
* be modified to be relative to `basepath` first (this should be done with patterns in project files, for example).
54-
* - Otherwise:
55-
* - Match all files where the pattern matches any part of the file's canonical absolute path up until a
56-
* path separator or the end of the pathname, and the matching part directly follows a path separator.
36+
* Path matching rules:
37+
* - All patterns are canonicalized (path separators vary by platform):
38+
* - '/./' => '/'
39+
* - '/dir/../' => '/'
40+
* - '//' => '/'
41+
* - Trailing slashes are removed (root slash is preserved)
42+
* - Patterns can contain globs:
43+
* - '**' matches any number of characters including path separators.
44+
* - '*' matches any number of characters except path separators.
45+
* - '?' matches any single character except path separators.
46+
* - If a pattern looks like an absolute path (e.g. starts with '/', but varies by platform):
47+
* - Match all files where the pattern matches the start of the file's canonical absolute path up until a path
48+
* separator or the end of the pathname.
49+
* - If a pattern looks like a relative path, i.e. is '.' or '..', or
50+
* starts with '.' or '..' followed by a path separator:
51+
* - The pattern is interpreted as a path relative to `basepath` and then converted to an absolute path and
52+
* treated as such according to the above procedure. If the pattern is relative to some other directory, it should
53+
* be modified to be relative to `basepath` first (this should be done with patterns in project files, for example).
54+
* - Otherwise:
55+
* - Match all files where the pattern matches any part of the file's canonical absolute path up until a
56+
* path separator or the end of the pathname, and the matching part directly follows a path separator.
57+
*
58+
* TODO: Handle less common windows windows syntaxes:
59+
* - Drive-specific relative path: C:dir\foo.cpp
60+
* - Root-relative path: \dir\foo.cpp
5761
**/
5862

5963
/**
60-
* @brief Simple path matching for ignoring paths in CLI.
64+
* @brief Syntactic path matching for ignoring paths in CLI.
6165
*/
6266
class CPPCHECKLIB PathMatch {
6367
public:
6468

6569
/**
66-
* @brief Match mode.
70+
* @brief Path syntax.
71+
*
72+
* windows: Case insensitive, forward and backward slashes, UNC or drive letter root.
73+
* unix: Case sensitive, forward slashes, slash root.
6774
*
68-
* scase: Case sensitive.
69-
* icase: Case insensitive.
7075
*/
71-
enum class Mode : std::uint8_t {
72-
scase,
73-
icase,
76+
enum class Syntax : std::uint8_t {
77+
windows,
78+
unix,
7479
};
7580

7681
/**
77-
* @brief The default mode for the current platform.
82+
* @brief The default syntax for the current platform.
7883
*/
7984
#ifdef _WIN32
80-
static constexpr Mode platform_mode = Mode::icase;
85+
static constexpr Syntax platform_syntax = Syntax::windows;
8186
#else
82-
static constexpr Mode platform_mode = Mode::scase;
87+
static constexpr Syntax platform_syntax = Syntax::unix;
8388
#endif
8489

8590
/**
8691
* The constructor.
8792
*
8893
* @param patterns List of patterns.
8994
* @param basepath Path to which patterns and matched paths are relative, when applicable.
90-
* @param mode Case sensitivity mode.
95+
* @param syntax Path syntax.
9196
*/
92-
explicit PathMatch(std::vector<std::string> patterns = {}, std::string basepath = std::string(), Mode mode = platform_mode);
97+
explicit PathMatch(std::vector<std::string> patterns = {}, std::string basepath = std::string(), Syntax syntax = platform_syntax);
9398

9499
/**
95100
* @brief Match path against list of patterns.
@@ -105,10 +110,10 @@ class CPPCHECKLIB PathMatch {
105110
* @param pattern Pattern to use.
106111
* @param path Path to match.
107112
* @param basepath Path to which the pattern and path is relative, when applicable.
108-
* @param mode Case sensitivity mode.
113+
* @param syntax Path syntax.
109114
* @return true if the pattern matches the path, false otherwise.
110115
*/
111-
static bool match(const std::string &pattern, const std::string &path, const std::string &basepath = std::string(), Mode mode = platform_mode);
116+
static bool match(const std::string &pattern, const std::string &path, const std::string &basepath = std::string(), Syntax syntax = platform_syntax);
112117

113118
/**
114119
* @brief Check if a pattern is a relative path name.
@@ -152,9 +157,12 @@ class CPPCHECKLIB PathMatch {
152157
friend class TestPathMatch;
153158
class PathIterator;
154159

160+
/* List of patterns */
155161
std::vector<std::string> mPatterns;
162+
/* Base path to with patterns and paths are relative */
156163
std::string mBasepath;
157-
Mode mMode;
164+
/* The syntax to use */
165+
Syntax mSyntax;
158166
};
159167

160168
/**
@@ -167,7 +175,7 @@ class CPPCHECKLIB PathMatch {
167175
* Both strings are optional. If both strings are present, then they're concatenated with a slash
168176
* (subject to canonicalization).
169177
*
170-
* Double-dots at the root level are removed. The root slash is preserved, other trailing slashes are removed.
178+
* Double-dots at the root level are removed. Trailing slashes are removed, the root is preserved.
171179
*
172180
* Doing the iteration in reverse allows canonicalization to be performed without lookahead. This is useful
173181
* for comparing path strings, potentially relative to different base paths, without having to do prior string
@@ -182,40 +190,65 @@ class CPPCHECKLIB PathMatch {
182190
class PathMatch::PathIterator {
183191
public:
184192
/* Create from a pattern and base path */
185-
static PathIterator fromPattern(const std::string &pattern, const std::string &basepath, bool icase)
193+
static PathIterator fromPattern(const std::string &pattern, const std::string &basepath, Syntax syntax)
186194
{
187195
if (isRelativePattern(pattern))
188-
return PathIterator(basepath.c_str(), pattern.c_str(), icase);
189-
return PathIterator(pattern.c_str(), nullptr, icase);
196+
return PathIterator(basepath.c_str(), pattern.c_str(), syntax);
197+
return PathIterator(pattern.c_str(), nullptr, syntax);
190198
}
191199

192200
/* Create from path and base path */
193-
static PathIterator fromPath(const std::string &path, const std::string &basepath, bool icase)
201+
static PathIterator fromPath(const std::string &path, const std::string &basepath, Syntax syntax)
194202
{
195203
if (Path::isAbsolute(path))
196-
return PathIterator(path.c_str(), nullptr, icase);
197-
return PathIterator(basepath.c_str(), path.c_str(), icase);
204+
return PathIterator(path.c_str(), nullptr, syntax);
205+
return PathIterator(basepath.c_str(), path.c_str(), syntax);
198206
}
199207

200208
/* Constructor */
201-
explicit PathIterator(const char *path_a = nullptr, const char *path_b = nullptr, bool lower = false) :
202-
mStart{path_a, path_b}, mLower(lower)
209+
explicit PathIterator(const char *path_a = nullptr, const char *path_b = nullptr, Syntax syntax = platform_syntax) :
210+
mStart{path_a, path_b}, mSyntax(syntax)
203211
{
212+
const auto issep = [syntax] (char c) { return c == '/' || (syntax == Syntax::windows && c == '\\'); };
213+
const auto isdrive = [] (char c) { return (c >= 'A' && c <= 'Z' ) || (c >= 'a' && c <= 'z'); };
214+
204215
for (int i = 0; i < 2; i++) {
205-
mEnd[i] = mStart[i];
216+
const char *&p = mEnd[i];
217+
p = mStart[i];
206218

207-
if (mStart[i] == nullptr || *mStart[i] == '\0')
219+
if (p == nullptr || *p == '\0')
208220
continue;
209221

210-
if (mPos.l != 0)
222+
if (mPos.l == 0) {
223+
/* Check length of root component */
224+
if (issep(p[0])) {
225+
mPos.l++;
226+
if (syntax == Syntax::windows && issep(p[1])) {
227+
mPos.l++;
228+
if (p[2] == '.' || p[2] == '?') {
229+
mPos.l++;
230+
if (issep(p[3]))
231+
mPos.l++;
232+
}
233+
}
234+
} else if (syntax == Syntax::windows && isdrive(p[0]) && p[1] == ':') {
235+
mPos.l += 2;
236+
if (issep(p[2]))
237+
mPos.l++;
238+
}
239+
p += mPos.l;
240+
mRootLength = mPos.l;
241+
} else {
242+
/* Add path separator */
211243
mPos.l++;
244+
}
212245

213-
while (*mEnd[i] != '\0') {
214-
mEnd[i]++;
246+
while (*p != '\0') {
247+
p++;
215248
mPos.l++;
216249
}
217250

218-
mPos.p = mEnd[i];
251+
mPos.p = p;
219252
}
220253

221254
if (mPos.l == 0)
@@ -280,19 +313,19 @@ class PathMatch::PathIterator {
280313

281314
char c = mPos.p[-1];
282315

283-
if (c == '\\')
284-
return '/';
285-
286-
if (mLower)
316+
if (mSyntax == Syntax::windows) {
317+
if (c == '\\')
318+
return '/';
287319
return std::tolower(c);
320+
}
288321

289322
return c;
290323
}
291324

292325
/* Do canonicalization on a path component boundary */
293326
void skips(bool leadsep)
294327
{
295-
while (mPos.l != 0) {
328+
while (mPos.l > mRootLength) {
296329
Pos pos = mPos;
297330

298331
if (leadsep) {
@@ -312,7 +345,7 @@ class PathMatch::PathIterator {
312345
/* Skip 'dir/../' */
313346
nextc();
314347
skips(false);
315-
while (mPos.l != 0 && current() != '/')
348+
while (mPos.l > mRootLength && current() != '/')
316349
nextc();
317350
continue;
318351
}
@@ -323,7 +356,7 @@ class PathMatch::PathIterator {
323356
/* Skip leading './' */
324357
break;
325358
}
326-
} else if (c == '/' && mPos.l != 1) {
359+
} else if (c == '/') {
327360
/* Skip double separator (keep root) */
328361
nextc();
329362
leadsep = false;
@@ -371,8 +404,10 @@ class PathMatch::PathIterator {
371404
const char *mEnd[2] {};
372405
/* Current position */
373406
Pos mPos {};
374-
/* Lowercase conversion flag */
375-
bool mLower;
407+
/* Length of the root component */
408+
std::size_t mRootLength {};
409+
/* Syntax */
410+
Syntax mSyntax;
376411
};
377412

378413
/// @}

test/cli/proj2_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_gui_project_loads_compile_commands_2(tmp_path):
100100
proj_dir = tmp_path / 'proj2'
101101
shutil.copytree(__proj_dir, proj_dir)
102102
__create_compile_commands(proj_dir)
103-
exclude_path_1 = 'proj2/b/'
103+
exclude_path_1 = 'proj2/b'
104104
create_gui_project_file(os.path.join(proj_dir, 'test.cppcheck'),
105105
import_project='compile_commands.json',
106106
exclude_paths=[exclude_path_1])
@@ -157,7 +157,7 @@ def test_gui_project_loads_relative_vs_solution_2(tmp_path):
157157
def test_gui_project_loads_relative_vs_solution_with_exclude(tmp_path):
158158
proj_dir = tmp_path / 'proj2'
159159
shutil.copytree(__proj_dir, proj_dir)
160-
create_gui_project_file(os.path.join(tmp_path, 'test.cppcheck'), root_path='proj2', import_project='proj2/proj2.sln', exclude_paths=['b/'])
160+
create_gui_project_file(os.path.join(tmp_path, 'test.cppcheck'), root_path='proj2', import_project='proj2/proj2.sln', exclude_paths=['b'])
161161
ret, stdout, stderr = cppcheck(['--project=test.cppcheck'], cwd=tmp_path)
162162
assert ret == 0, stdout
163163
assert stderr == __ERR_A

0 commit comments

Comments
 (0)