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
16 changes: 8 additions & 8 deletions cli/processexecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ ProcessExecutor::ProcessExecutor(const std::list<FileWithDetails> &files, const
namespace {
class PipeWriter : public ErrorLogger {
public:
enum PipeSignal : std::uint8_t {REPORT_OUT='1',REPORT_ERROR='2',REPORT_SUPPR_INLINE='3',CHILD_END='5'};
enum PipeSignal : std::uint8_t {REPORT_OUT='1',REPORT_ERROR='2',REPORT_SUPPR_INLINE='3',REPORT_SUPPR='4',CHILD_END='5'};

explicit PipeWriter(int pipe) : mWpipe(pipe) {}

Expand All @@ -93,12 +93,11 @@ namespace {
void writeSuppr(const SuppressionList &supprs) const {
for (const auto& suppr : supprs.getSuppressions())
{
if (!suppr.isInline)
continue;

writeToPipe(REPORT_SUPPR_INLINE, suppressionToString(suppr));
if (suppr.isInline)
writeToPipe(REPORT_SUPPR_INLINE, suppressionToString(suppr));
else if (suppr.checked)
writeToPipe(REPORT_SUPPR, suppressionToString(suppr));
}
// TODO: update suppression states?
}

void writeEnd(const std::string& str) const {
Expand Down Expand Up @@ -179,6 +178,7 @@ bool ProcessExecutor::handleRead(int rpipe, unsigned int &result, const std::str
if (type != PipeWriter::REPORT_OUT &&
type != PipeWriter::REPORT_ERROR &&
type != PipeWriter::REPORT_SUPPR_INLINE &&
type != PipeWriter::REPORT_SUPPR &&
type != PipeWriter::CHILD_END) {
std::cerr << "#### ThreadExecutor::handleRead(" << filename << ") invalid type " << int(type) << std::endl;
std::exit(EXIT_FAILURE);
Expand Down Expand Up @@ -230,7 +230,7 @@ bool ProcessExecutor::handleRead(int rpipe, unsigned int &result, const std::str

if (hasToLog(msg))
mErrorLogger.reportErr(msg);
} else if (type == PipeWriter::REPORT_SUPPR_INLINE) {
} else if (type == PipeWriter::REPORT_SUPPR_INLINE || type == PipeWriter::REPORT_SUPPR) {
if (!buf.empty()) {
// TODO: avoid string splitting
auto parts = splitString(buf, ';');
Expand All @@ -241,7 +241,7 @@ bool ProcessExecutor::handleRead(int rpipe, unsigned int &result, const std::str
std::exit(EXIT_FAILURE);
}
auto suppr = SuppressionList::parseLine(parts[0]);
suppr.isInline = true;
suppr.isInline = (type == PipeWriter::REPORT_SUPPR_INLINE);
suppr.checked = parts[1] == "1";
suppr.matched = parts[2] == "1";
const std::string err = mSuppressions.nomsg.addSuppression(suppr);
Expand Down
4 changes: 2 additions & 2 deletions gui/checkthread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,8 @@ void CheckThread::parseClangErrors(const QString &tool, const QString &file0, QS

bool CheckThread::isSuppressed(const SuppressionList::ErrorMessage &errorMessage) const
{
return std::any_of(mSuppressionsUi.cbegin(), mSuppressionsUi.cend(), [&](const SuppressionList::Suppression& s) {
return s.isSuppressed(errorMessage);
return std::any_of(mSuppressionsUi.cbegin(), mSuppressionsUi.cend(), [&](const SuppressionList::Suppression& s) -> bool {
return s.isSuppressed(errorMessage) == SuppressionList::Suppression::Result::Matched;
});
}

Expand Down
8 changes: 8 additions & 0 deletions lib/cppcheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,14 @@ unsigned int CppCheck::checkClang(const FileWithDetails &file)

unsigned int CppCheck::check(const FileWithDetails &file)
{
// TODO: handle differently?
// dummy call to make sure wildcards are being flagged as checked in case isSuppressed() is never called
Comment thread
danmar marked this conversation as resolved.
{
// the empty ID is intentional for now - although it should not be allowed
ErrorMessage msg({}, file.spath(), Severity::information, "", "", Certainty::normal);
(void)mSuppressions.nomsg.isSuppressed(SuppressionList::ErrorMessage::fromErrorMessage(msg, {}), true);
}

if (mSettings.clang)
return checkClang(file);

Expand Down
69 changes: 47 additions & 22 deletions lib/suppressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,13 @@ bool SuppressionList::updateSuppressionState(const SuppressionList::Suppression&
auto foundSuppression = std::find_if(mSuppressions.begin(), mSuppressions.end(),
std::bind(&Suppression::isSameParameters, &suppression, std::placeholders::_1));
if (foundSuppression != mSuppressions.end()) {
// Update matched state of existing global suppression
if (!suppression.isLocal() && suppression.matched)
foundSuppression->matched = suppression.matched;
// Update state of existing global suppression
if (!suppression.isLocal()) {
if (suppression.checked)
foundSuppression->checked = true;
if (suppression.matched)
foundSuppression->matched = true;
}
return true;
}

Expand Down Expand Up @@ -373,26 +377,32 @@ bool SuppressionList::Suppression::parseComment(std::string comment, std::string
return true;
}

bool SuppressionList::Suppression::isSuppressed(const SuppressionList::ErrorMessage &errmsg) const
SuppressionList::Suppression::Result SuppressionList::Suppression::isSuppressed(const SuppressionList::ErrorMessage &errmsg) const
{
if (hash > 0 && hash != errmsg.hash)
return false;
if (!errorId.empty() && !matchglob(errorId, errmsg.errorId))
return false;
if (type == SuppressionList::Type::macro) {
if (errmsg.macroNames.count(macroName) == 0)
return false;
return Result::None;
if (hash > 0 && hash != errmsg.hash)
return Result::Checked;
if (!errorId.empty() && !matchglob(errorId, errmsg.errorId))
return Result::Checked;
} else {
if (!fileName.empty() && !matchglob(fileName, errmsg.getFileName()))
return false;
return Result::None;
if ((SuppressionList::Type::unique == type) && (lineNumber != NO_LINE) && (lineNumber != errmsg.lineNumber)) {
if (!thisAndNextLine || lineNumber + 1 != errmsg.lineNumber)
return false;
return Result::None;
}
if (hash > 0 && hash != errmsg.hash)
return Result::Checked;
// the empty check is a hack to allow wildcard suppressions on IDs to be marked as checked
if (!errorId.empty() && (errmsg.errorId.empty() || !matchglob(errorId, errmsg.errorId)))
return Result::Checked;
Comment thread
danmar marked this conversation as resolved.
if ((SuppressionList::Type::block == type) && ((errmsg.lineNumber < lineBegin) || (errmsg.lineNumber > lineEnd)))
return false;
return Result::Checked;
}
if (!symbolName.empty()) {
bool matchedSymbol = false;
for (std::string::size_type pos = 0; pos < errmsg.symbolNames.size();) {
const std::string::size_type pos2 = errmsg.symbolNames.find('\n',pos);
std::string symname;
Expand All @@ -403,21 +413,31 @@ bool SuppressionList::Suppression::isSuppressed(const SuppressionList::ErrorMess
symname = errmsg.symbolNames.substr(pos,pos2-pos);
pos = pos2+1;
}
if (matchglob(symbolName, symname))
return true;
if (matchglob(symbolName, symname)) {
matchedSymbol = true;
break;
}
}
return false;
if (!matchedSymbol)
return Result::Checked;
}
return true;
return Result::Matched;
}

bool SuppressionList::Suppression::isMatch(const SuppressionList::ErrorMessage &errmsg)
{
if (!isSuppressed(errmsg))
switch (isSuppressed(errmsg)) {
case Result::None:
return false;
matched = true;
checked = true;
return true;
case Result::Checked:
checked = true;
return false;
case Result::Matched:
checked = true;
matched = true;
return true;
}
cppcheck::unreachable();
}

// cppcheck-suppress unusedFunction - used by GUI only
Expand Down Expand Up @@ -525,7 +545,9 @@ std::list<SuppressionList::Suppression> SuppressionList::getUnmatchedLocalSuppre
for (const Suppression &s : mSuppressions) {
if (s.isInline)
continue;
if (s.matched || ((s.lineNumber != Suppression::NO_LINE) && !s.checked))
if (s.matched)
continue;
if ((s.lineNumber != Suppression::NO_LINE) && !s.checked)
continue;
if (s.type == SuppressionList::Type::macro)
continue;
Expand All @@ -550,7 +572,9 @@ std::list<SuppressionList::Suppression> SuppressionList::getUnmatchedGlobalSuppr
for (const Suppression &s : mSuppressions) {
if (s.isInline)
continue;
if (s.matched || ((s.lineNumber != Suppression::NO_LINE) && !s.checked))
if (s.matched)
continue;
if (!s.checked && s.isWildcard())
continue;
if (s.hash > 0)
continue;
Expand All @@ -571,6 +595,7 @@ std::list<SuppressionList::Suppression> SuppressionList::getUnmatchedInlineSuppr
for (const SuppressionList::Suppression &s : SuppressionList::mSuppressions) {
if (!s.isInline)
continue;
// TODO: remove this and markUnmatchedInlineSuppressionsAsChecked()?
if (!s.checked)
continue;
if (s.matched)
Expand Down
18 changes: 14 additions & 4 deletions lib/suppressions.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,24 @@ class CPPCHECKLIB SuppressionList {
*/
bool parseComment(std::string comment, std::string *errorMessage);

bool isSuppressed(const ErrorMessage &errmsg) const;
enum class Result : std::uint8_t {
None,
Checked,
Matched
};

Result isSuppressed(const ErrorMessage &errmsg) const;

bool isMatch(const ErrorMessage &errmsg);

std::string getText() const;

bool isWildcard() const {
return fileName.find_first_of("?*") != std::string::npos;
}

bool isLocal() const {
return !fileName.empty() && fileName.find_first_of("?*") == std::string::npos;
return !fileName.empty() && !isWildcard();
}

bool isSameParameters(const Suppression &other) const {
Expand All @@ -149,8 +159,8 @@ class CPPCHECKLIB SuppressionList {
std::string macroName;
std::size_t hash{};
bool thisAndNextLine{}; // Special case for backwards compatibility: { // cppcheck-suppress something
bool matched{};
bool checked{}; // for inline suppressions, checked or not
bool matched{}; /** This suppression was fully matched in an isSuppressed() call */
bool checked{}; /** This suppression applied to code which was being analyzed but did not match the error in an isSuppressed() call */
bool isInline{};

enum : std::int8_t { NO_LINE = -1 };
Expand Down
19 changes: 19 additions & 0 deletions test/cli/inline-suppress_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,4 +437,23 @@ def test_unused_function_disabled_unmatched():
'{}unusedFunctionUnmatched.cpp:5:0: information: Unmatched suppression: uninitvar [unmatchedSuppression]'.format(__proj_inline_suppres_path)
]
assert stdout == ''
assert ret == 0, stdout


def test_unmatched_cfg():
# make sure we do not report unmatched inline suppressions from inactive code blocks
args = [
'-q',
'--template=simple',
'--enable=warning,information',
'--inline-suppr',
'proj-inline-suppress/cfg.c'
]

ret, stdout, stderr = cppcheck(args, cwd=__script_dir)
assert stderr.splitlines() == [
'{}cfg.c:10:0: information: Unmatched suppression: id [unmatchedSuppression]'.format(__proj_inline_suppres_path),
'{}cfg.c:14:0: information: Unmatched suppression: id [unmatchedSuppression]'.format(__proj_inline_suppres_path),
]
assert stdout == ''
assert ret == 0, stdout
67 changes: 66 additions & 1 deletion test/cli/other_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3417,4 +3417,69 @@ def test_clang_tidy(tmpdir): # #12053

@pytest.mark.skipif(not has_clang_tidy, reason='clang-tidy is not available')
def test_clang_tidy_project(tmpdir):
__test_clang_tidy(tmpdir, True)
__test_clang_tidy(tmpdir, True)


def test_suppress_unmatched_wildcard(tmp_path): # #13660
test_file = tmp_path / 'test.c'
with open(test_file, 'wt') as f:
f.write(
"""void f()
{
(void)(*((int*)0));
}
""")

# need to run in the temporary folder because the path of the suppression has to match
args = [
'-q',
'--template=simple',
'--enable=information',
'--suppress=nullPointer:test*.c', # checked and matched
'--suppress=id:test*.c', # checked and unmatched
'--suppress=id2:test*.c', # checked and unmatched
'--suppress=id2:tes?.c', # checked and unmatched
'--suppress=*:test*.c', # checked and unmatched
'--suppress=id:test*.cpp', # unchecked
'--suppress=id2:test?.cpp', # unchecked
'test.c'
]
exitcode, stdout, stderr = cppcheck(args, cwd=tmp_path)
assert exitcode == 0, stdout
assert stdout.splitlines() == []
# TODO: invalid locations - see #13659
assert stderr.splitlines() == [
'test*.c:-1:0: information: Unmatched suppression: id [unmatchedSuppression]',
'test*.c:-1:0: information: Unmatched suppression: id2 [unmatchedSuppression]',
'tes?.c:-1:0: information: Unmatched suppression: id2 [unmatchedSuppression]'
]


def test_suppress_unmatched_wildcard_unchecked(tmp_path):
# make sure that unmatched wildcards suppressions are reported if files matching the expressions were processesd
# but isSuppressed() has never been called (i.e. no findings in file at all)
test_file = tmp_path / 'test.c'
with open(test_file, 'wt') as f:
f.write("""void f() {}""")

# need to run in the temporary folder because the path of the suppression has to match
args = [
'-q',
'--template=simple',
'--enable=information',
'--suppress=id:test*.c',
'--suppress=id:tes?.c',
'--suppress=id2:*',
'--suppress=*:test*.c',
'test.c'
]
exitcode, stdout, stderr = cppcheck(args, cwd=tmp_path)
assert exitcode == 0, stdout
assert stdout.splitlines() == []
# TODO: invalid locations - see #13659
assert stderr.splitlines() == [
'test*.c:-1:0: information: Unmatched suppression: id [unmatchedSuppression]',
'tes?.c:-1:0: information: Unmatched suppression: id [unmatchedSuppression]',
'*:-1:0: information: Unmatched suppression: id2 [unmatchedSuppression]',
'test*.c:-1:0: information: Unmatched suppression: * [unmatchedSuppression]'
]
15 changes: 15 additions & 0 deletions test/cli/proj-inline-suppress/cfg.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
void f()
{
#if VER_CHECK(3, 1, 6)
// cppcheck-suppress id
(void)0;
#endif

#if DEF_1
// cppcheck-suppress id
(void)0;
#endif

// cppcheck-suppress id
(void)0;
}
Loading