Skip to content

Commit 3c4e8e0

Browse files
committed
Update rules for relative patterns
1 parent 625d613 commit 3c4e8e0

3 files changed

Lines changed: 57 additions & 22 deletions

File tree

lib/importproject.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,7 +1254,7 @@ static std::list<std::string> readXmlStringList(const tinyxml2::XMLElement *node
12541254
continue;
12551255
const char *attr = attribute ? child->Attribute(attribute) : child->GetText();
12561256
if (attr)
1257-
ret.push_back(joinRelativePath(path, attr));
1257+
ret.emplace_back(joinRelativePath(path, attr));
12581258
}
12591259
return ret;
12601260
}
@@ -1266,12 +1266,8 @@ static std::list<std::string> readXmlPathMatchList(const tinyxml2::XMLElement *n
12661266
if (strcmp(child->Name(), name) != 0)
12671267
continue;
12681268
const char *attr = attribute ? child->Attribute(attribute) : child->GetText();
1269-
if (attr) {
1270-
if (attr[0] == '.')
1271-
ret.push_back(joinRelativePath(path, attr));
1272-
else
1273-
ret.emplace_back(attr);
1274-
}
1269+
if (attr)
1270+
ret.emplace_back(PathMatch::joinRelativePattern(path, attr));
12751271
}
12761272
return ret;
12771273
}

lib/pathmatch.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,12 @@ bool PathMatch::match(const std::string &pattern, const std::string &path, const
4747

4848
/* A "real" path is absolute or relative to the base path. A pattern that isn't "real" can match at any
4949
* path component boundary. */
50-
bool real = Path::isAbsolute(pattern) || pattern[0] == '.';
50+
bool real = Path::isAbsolute(pattern) || isRelativePattern(pattern);
5151

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

lib/pathmatch.h

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,25 @@
3434

3535
/**
3636
* Path matching rules:
37-
* - All patterns are simplified first (path separators vary by platform):
37+
* - All patterns are canonicalized (path separators vary by platform):
3838
* - '/./' => '/'
3939
* - '/dir/../' => '/'
4040
* - '//' => '/'
41-
* - Trailing slashes are removed
41+
* - Trailing slashes are removed (root slash is preserved)
4242
* - Patterns can contain globs:
4343
* - '**' matches any number of characters including path separators.
4444
* - '*' matches any number of characters except path separators.
4545
* - '?' matches any single character except path separators.
4646
* - 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 simplified absolute path up until a path
47+
* - Match all files where the pattern matches the start of the file's canonical absolute path up until a path
4848
* separator or the end of the pathname.
49-
* - If a pattern starts with '.':
49+
* - If a pattern looks like a relative path, i.e. is '.' or '..', or
50+
* starts with '.' or '..' followed by a path separator:
5051
* - The pattern is interpreted as a path relative to `basepath` and then converted to an absolute path and
5152
* treated as such according to the above procedure. If the pattern is relative to some other directory, it should
5253
* be modified to be relative to `basepath` first (this should be done with patterns in project files, for example).
5354
* - Otherwise:
54-
* - Match all files where the pattern matches any part of the file's simplified absolute path up until a
55+
* - Match all files where the pattern matches any part of the file's canonical absolute path up until a
5556
* path separator or the end of the pathname, and the matching part directly follows a path separator.
5657
**/
5758

@@ -66,15 +67,15 @@ class CPPCHECKLIB PathMatch {
6667
*
6768
* scase: Case sensitive.
6869
* icase: Case insensitive.
69-
**/
70+
*/
7071
enum class Mode : std::uint8_t {
7172
scase,
7273
icase,
7374
};
7475

7576
/**
7677
* @brief The default mode for the current platform.
77-
**/
78+
*/
7879
#ifdef _WIN32
7980
static constexpr Mode platform_mode = Mode::icase;
8081
#else
@@ -109,6 +110,44 @@ class CPPCHECKLIB PathMatch {
109110
*/
110111
static bool match(const std::string &pattern, const std::string &path, const std::string &basepath = std::string(), Mode mode = platform_mode);
111112

113+
/**
114+
* @brief Check if a pattern is a relative path name.
115+
*
116+
* @param pattern Pattern to check.
117+
* @return true if the pattern has the form of a relative path name pattern.
118+
*/
119+
static bool isRelativePattern(const std::string &pattern)
120+
{
121+
if (pattern.empty() || pattern[0] != '.')
122+
return false;
123+
124+
if (pattern.size() < 2 || pattern[1] == '/' || pattern[1] == '\\')
125+
return true;
126+
127+
if (pattern[1] != '.')
128+
return false;
129+
130+
if (pattern.size() < 3 || pattern[2] == '/' || pattern[1] == '\\')
131+
return true;
132+
133+
return false;
134+
}
135+
136+
/**
137+
* @brief Join a pattern with a base path.
138+
*
139+
* @param basepath The base path to join the pattern to.
140+
* @param pattern The pattern to join.
141+
* @return The pattern appended to the base path with a separator if the pattern is a relative
142+
* path name, otherwise just returns pattern.
143+
*/
144+
static std::string joinRelativePattern(const std::string &basepath, const std::string &pattern)
145+
{
146+
if (isRelativePattern(pattern))
147+
return Path::join(basepath, pattern);
148+
return pattern;
149+
}
150+
112151
private:
113152
friend class TestPathMatch;
114153
class PathIterator;
@@ -142,16 +181,16 @@ class CPPCHECKLIB PathMatch {
142181
**/
143182
class PathMatch::PathIterator {
144183
public:
145-
/* Create from a pattern and base path, patterns must begin with '.' to be considered relative */
146-
static PathIterator from_pattern(const std::string &pattern, const std::string &basepath, bool icase)
184+
/* Create from a pattern and base path */
185+
static PathIterator fromPattern(const std::string &pattern, const std::string &basepath, bool icase)
147186
{
148-
if (!pattern.empty() && pattern[0] == '.')
187+
if (isRelativePattern(pattern))
149188
return PathIterator(basepath.c_str(), pattern.c_str(), icase);
150189
return PathIterator(pattern.c_str(), nullptr, icase);
151190
}
152191

153192
/* Create from path and base path */
154-
static PathIterator from_path(const std::string &path, const std::string &basepath, bool icase)
193+
static PathIterator fromPath(const std::string &path, const std::string &basepath, bool icase)
155194
{
156195
if (Path::isAbsolute(path))
157196
return PathIterator(path.c_str(), nullptr, icase);
@@ -270,7 +309,7 @@ class PathMatch::PathIterator {
270309
nextc();
271310
c = current();
272311
if (c == '/') {
273-
/* Skip '<name>/../' */
312+
/* Skip 'dir/../' */
274313
nextc();
275314
skips(false);
276315
while (mPos.l != 0 && current() != '/')

0 commit comments

Comments
 (0)