Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 134 additions & 19 deletions lib/settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <iostream>
#include <cctype> // std::isdigit, std::isalnum, etc
#include <set>
#include <stack>

Settings::Settings()
{
Expand Down Expand Up @@ -116,6 +117,137 @@ std::string Settings::Suppressions::parseFile(std::istream &istr)
return "";
}

bool Settings::Suppressions::FileMatcher::match(const std::string &pattern, const std::string &name)
{
const char *p = pattern.c_str();
const char *n = name.c_str();
std::stack<std::pair<const char *, const char *> > 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
{
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 tray, 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++;
}
}

std::string Settings::Suppressions::FileMatcher::addFile(const std::string &name, unsigned int line)
{
if (name.find_first_of("*?") != std::string::npos)
{
for (std::string::const_iterator i = name.begin(); i != name.end(); ++i)
{
if (*i == '*')
{
std::string::const_iterator j = i + 1;
if (j != name.end() && (*j == '*' || *j == '?'))
{
return "Failed to add suppression. Syntax error in glob.";
}
}
}
_globs[name].insert(line);
}
else
{
_files[name].insert(line);
}
return "";
}

bool Settings::Suppressions::FileMatcher::isSuppressed(const std::string &file, unsigned int line)
{
// Check are all errors of this type filtered out
if (_files.find("") != _files.end())
return true;

std::set<unsigned int> lineset;

std::map<std::string, std::set<unsigned int> >::const_iterator f = _files.find(file);
if (f != _files.end())
{
lineset.insert(f->second.begin(), f->second.end());
}
for (std::map<std::string, std::set<unsigned int> >::iterator g = _globs.begin(); g != _globs.end(); ++g)
{
if (match(g->first, file))
{
lineset.insert(g->second.begin(), g->second.end());
}
}

if (lineset.empty())
return false;

// Check should all errors in this file be filtered out
if (lineset.find(0U) != lineset.end())
return true;

if (lineset.find(line) == lineset.end())
return false;

return true;
}

std::string Settings::Suppressions::addSuppression(const std::string &errorId, const std::string &file, unsigned int line)
{
// Check that errorId is valid..
Expand All @@ -135,32 +267,15 @@ std::string Settings::Suppressions::addSuppression(const std::string &errorId, c
}
}

_suppressions[errorId][file].push_back(line);
_suppressions[errorId][file].sort();

return "";
return _suppressions[errorId].addFile(file, line);
}

bool Settings::Suppressions::isSuppressed(const std::string &errorId, const std::string &file, unsigned int line)
{
if (_suppressions.find(errorId) == _suppressions.end())
return false;

// Check are all errors of this type filtered out
if (_suppressions[errorId].find("") != _suppressions[errorId].end())
return true;

if (_suppressions[errorId].find(file) == _suppressions[errorId].end())
return false;

// Check should all errors in this file be filtered out
if (std::find(_suppressions[errorId][file].begin(), _suppressions[errorId][file].end(), 0) != _suppressions[errorId][file].end())
return true;

if (std::find(_suppressions[errorId][file].begin(), _suppressions[errorId][file].end(), line) == _suppressions[errorId][file].end())
return false;

return true;
return _suppressions[errorId].isSuppressed(file, line);
}

std::string Settings::addEnabled(const std::string &str)
Expand Down
37 changes: 36 additions & 1 deletion lib/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <string>
#include <istream>
#include <map>
#include <set>

/// @addtogroup Core
/// @{
Expand Down Expand Up @@ -135,8 +136,42 @@ class Settings
class Suppressions
{
private:
class FileMatcher
{
private:
/** @brief List of filenames suppressed. */
std::map<std::string, std::set<unsigned int> > _files;
/** @brief List of globs suppressed. */
std::map<std::string, std::set<unsigned int> > _globs;

/**
* @brief Match a name against a glob pattern.
* @param pattern The glob pattern to match.
* @param name The filename to match against the glob pattern.
* @return match success
*/
static bool match(const std::string &pattern, const std::string &name);

public:
/**
* @brief Add a file or glob (and line number).
* @param name File name or glob pattern
* @param line Line number
* @return error message. empty upon success
*/
std::string addFile(const std::string &name, unsigned int line);

/**
* @brief Returns true if the file name matches a previously added file or glob pattern.
* @param name File name to check
* @param line Line number
* @return true if this filename/line matches
*/
bool isSuppressed(const std::string &file, unsigned int line);
};

/** @brief List of error which the user doesn't want to see. */
std::map<std::string, std::map<std::string, std::list<unsigned int> > > _suppressions;
std::map<std::string, FileMatcher> _suppressions;
public:
/**
* @brief Don't show errors listed in the file.
Expand Down
3 changes: 2 additions & 1 deletion man/cppcheck.1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ Directory name is matched to all parts of the path.</para>
<term><option>--suppressions-list=&lt;file&gt;</option></term>
<listitem>
<para>Suppress warnings listed in the file. Filename and line are optional. The format of the single line in file is: [error id]:[filename]:[line].
You can use --template or --xml to see the error id.</para>
You can use --template or --xml to see the error id.
The filename may contain the wildcard characters * or ?.</para>
</listitem>
</varlistentry>
<varlistentry>
Expand Down
4 changes: 4 additions & 0 deletions man/manual.docbook
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,10 @@ gui/test.cpp,16,error,mismatchAllocDealloc,Mismatching allocation and deallocati
line flag. Copy and paste the <literal>id</literal> string from the XML
output.</para>

<para>The <literal>filename</literal> may include the wildcard characters
* or ?, which match any sequence of characters or any single character
respectively.</para>

<para>Here is an example:</para>

<programlisting>memleak:file1.cpp
Expand Down
36 changes: 36 additions & 0 deletions test/testsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class TestSettings : public TestFixture
TEST_CASE(suppressionsBadId1);
TEST_CASE(suppressionsDosFormat); // Ticket #1836
TEST_CASE(suppressionsFileNameWithColon); // Ticket #1919 - filename includes colon
TEST_CASE(suppressionsGlob);
}

void suppressionsBadId1()
Expand Down Expand Up @@ -63,6 +64,41 @@ class TestSettings : public TestFixture
ASSERT_EQUALS(false, suppressions.isSuppressed("errorid", "c:\\bar.cpp", 10));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "c:\\bar.cpp", 12));
}

void suppressionsGlob()
{
// Check for syntax errors in glob
{
Settings::Suppressions suppressions;
std::istringstream s("errorid:**.cpp\n");
ASSERT_EQUALS("Failed to add suppression. Syntax error in glob.", suppressions.parseFile(s));
}

// Check that globbing works
{
Settings::Suppressions suppressions;
std::istringstream s("errorid:x*.cpp\nerrorid:y?.cpp\nerrorid:test.c*");
ASSERT_EQUALS("", suppressions.parseFile(s));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "xyz.cpp", 1));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "xyz.cpp.cpp", 1));
ASSERT_EQUALS(false, suppressions.isSuppressed("errorid", "abc.cpp", 1));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "ya.cpp", 1));
ASSERT_EQUALS(false, suppressions.isSuppressed("errorid", "y.cpp", 1));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "test.c", 1));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "test.cpp", 1));
}

// Check that both a filename match and a glob match apply
{
Settings::Suppressions suppressions;
std::istringstream s("errorid:x*.cpp\nerrorid:xyz.cpp:1\nerrorid:a*.cpp:1\nerrorid:abc.cpp:2");
ASSERT_EQUALS("", suppressions.parseFile(s));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "xyz.cpp", 1));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "xyz.cpp", 2));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "abc.cpp", 1));
ASSERT_EQUALS(true, suppressions.isSuppressed("errorid", "abc.cpp", 2));
}
}
};

REGISTER_TEST(TestSettings)