Skip to content
Open
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ $(libcppdir)/standards.o: lib/standards.cpp externals/simplecpp/simplecpp.h lib/
$(libcppdir)/summaries.o: lib/summaries.cpp lib/addoninfo.h lib/analyzerinfo.h lib/checkers.h lib/config.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/sourcelocation.h lib/standards.h lib/summaries.h lib/symboldatabase.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/summaries.cpp

$(libcppdir)/suppressions.o: lib/suppressions.cpp externals/tinyxml2/tinyxml2.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/smallvector.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h
$(libcppdir)/suppressions.o: lib/suppressions.cpp externals/tinyxml2/tinyxml2.h lib/addoninfo.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/filesettings.h lib/library.h lib/mathlib.h lib/path.h lib/pathmatch.h lib/platform.h lib/settings.h lib/smallvector.h lib/standards.h lib/suppressions.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h lib/xml.h
$(CXX) ${INCLUDE_FOR_LIB} $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $(libcppdir)/suppressions.cpp

$(libcppdir)/templatesimplifier.o: lib/templatesimplifier.cpp lib/addoninfo.h lib/checkers.h lib/config.h lib/errorlogger.h lib/errortypes.h lib/library.h lib/mathlib.h lib/platform.h lib/settings.h lib/smallvector.h lib/standards.h lib/templatesimplifier.h lib/token.h lib/tokenize.h lib/tokenlist.h lib/utils.h lib/vfvalue.h
Expand Down
3 changes: 2 additions & 1 deletion cli/cppcheckexecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ static bool reportUnmatchedSuppressions(const std::list<SuppressionList::Suppres
if (!s.fileName.empty()) {
callStack.emplace_back(s.fileName, s.lineNumber, 0);
}
errorLogger.reportErr(::ErrorMessage(std::move(callStack), "", Severity::information, "Unmatched suppression: " + s.errorId, "unmatchedSuppression", Certainty::normal));
const std::string unmatchedSuppressionId = s.isPolyspace ? "unmatchedPolyspaceSuppression" : "unmatchedSuppression";
errorLogger.reportErr(::ErrorMessage(std::move(callStack), "", Severity::information, "Unmatched suppression: " + s.errorId, unmatchedSuppressionId, Certainty::normal));
err = true;
}
return err;
Expand Down
9 changes: 9 additions & 0 deletions lib/preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,19 @@ static void addInlineSuppressions(const simplecpp::TokenList &tokens, const Sett

bool onlyComments = true;

polyspace::Parser polyspaceParser(settings);

for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) {
if (!tok->comment) {
onlyComments = false;
continue;
}

if (polyspace::isPolyspaceComment(tok->str())) {
polyspaceParser.parse(tok->str(), tok->location.line, getRelativeFilename(tokens, tok, settings));
continue;
}

std::list<SuppressionList::Suppression> inlineSuppressions;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what exact handling we have here.. but I have the feeling we might want to reuse the same handling for cppcheck/polyspace inline suppressions so isn't it a good idea to add polyspace suppressions in inlineSuppressions also? For instance the filename used for cppcheck inline suppressions why don't we want to use the same for polyspace suppressions. There is handling for "file/begin"/"end" which could be useful for polyspace also I think..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Polyspace suppressions comments work quite differently from cppcheck suppression comments. So there isn't really any shared logic. But good point about the filename, I'll fix that.

if (!parseInlineSuppressionCommentToken(tokens, tok, inlineSuppressions, bad))
continue;
Expand Down Expand Up @@ -310,6 +317,8 @@ static void addInlineSuppressions(const simplecpp::TokenList &tokens, const Sett
for (const SuppressionList::Suppression & suppr: inlineSuppressionsBlockBegin)
// cppcheck-suppress useStlAlgorithm
bad.emplace_back(suppr.fileName, suppr.lineNumber, 0, "Suppress Begin: No matching end"); // TODO: set column

polyspaceParser.collect(suppressions);
}

void Preprocessor::inlineSuppressions(SuppressionList &suppressions)
Expand Down
331 changes: 331 additions & 0 deletions lib/suppressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "token.h"
#include "tokenize.h"
#include "tokenlist.h"
#include "settings.h"

#include <algorithm>
#include <cctype> // std::isdigit, std::isalnum, etc
Expand Down Expand Up @@ -647,3 +648,333 @@ std::string SuppressionList::Suppression::toString() const
}
return s;
}

polyspace::Parser::Parser(const Settings &settings)
{
const auto haveMisraAddon = std::any_of(settings.addonInfos.cbegin(),
settings.addonInfos.cend(),
[] (const AddonInfo &info) {
return info.name == "misra";
});

if (haveMisraAddon) {
mFamilyMap["MISRA-C3"] = "misra-c2012-";
mFamilyMap["MISRA2012"] = "misra-c2012-";
}

const auto matchArg = [&](const std::string &arg) {
const std::string args = settings.premiumArgs;
const std::string::size_type pos = args.find(arg);

if (pos == std::string::npos)
return false;

if (pos > 0 && args[pos - 1] != ' ')
return false;

return pos == args.size() - arg.size() || args[pos + arg.size()] == ' ';
};

if (matchArg("--misra-c-2012")) {
mFamilyMap["MISRA-C3"] = "premium-misra-c-2012-";
mFamilyMap["MISRA2012"] = "premium-misra-c-2012-";
}

if (matchArg("--misra-c-2023"))
mFamilyMap["MISRA-C-2023"] = "premium-misra-c-2023-";

if (matchArg("--misra-cpp-2008") || matchArg("--misra-c++-2008"))
mFamilyMap["MISRA-CPP"] = "premium-misra-cpp-2008-";

if (matchArg("--misra-cpp-2023") || matchArg("--misra-c++-2023"))
mFamilyMap["MISRA-CPP-2023"] = "premium-misra-cpp-2023-";

if (matchArg("--cert-c") || matchArg("--cert-c-2016"))
mFamilyMap["CERT-C"] = "premium-cert-c-";

if (matchArg("--cert-cpp") || matchArg("--cert-c++") ||
matchArg("--cert-cpp-2016") || matchArg("--cert-c++-2016"))
mFamilyMap["CERT-CPP"] = "premium-cert-cpp-";

if (matchArg("--autosar"))
mFamilyMap["AUTOSAR-CPP14"] = "premium-autosar-";
}

std::string polyspace::Parser::peekToken()
{
if (!mHasPeeked) {
mPeeked = nextToken();
mHasPeeked = true;
}
return mPeeked;
}

std::string polyspace::Parser::nextToken()
{
if (mHasPeeked) {
mHasPeeked = false;
return mPeeked;
}

std::string::size_type pos = 0;
while (mComment[pos] == ' ') {
pos++;
if (pos == mComment.size()) {
mComment = "";
return "";
}
}

if (mComment.compare(0, 2, "*/") == 0) {
mComment = "";
return "";
}

if (mComment[pos] == ':') {
mComment = mComment.substr(pos + 1);
return ":";
}

if (mComment[pos] == ',') {
mComment = mComment.substr(pos + 1);
return ",";
}

const char *stopChars;
std::string::size_type skip;
switch (mComment[pos]) {
case '\"':
stopChars = "\"";
skip = 1;
break;
case '[':
stopChars = "]";
skip = 1;
break;
default:
stopChars = " :,";
skip = 0;
break;
}

const std::string::size_type start = pos;
pos += skip;

if (pos == mComment.size()) {
mComment = "";
return "";
}

while (std::strchr(stopChars, mComment[pos]) == nullptr) {
pos++;
if (pos == mComment.size())
break;
}

if (pos == mComment.size())
skip = 0;

const std::string token = mComment.substr(start, pos - start + skip);
mComment = mComment.substr(pos + skip);

return token;
}

bool polyspace::Parser::parseAnnotation(polyspace::Annotation &annotation)
{
annotation.family = nextToken();
annotation.resultNames.clear();
annotation.extraComment = "";

if (annotation.family.empty())
return false;

if (nextToken() != ":")
return false;

for (;;) {
const std::string resultName = nextToken();

if (resultName.empty())
return false;

annotation.resultNames.push_back(resultName);

if (peekToken().substr(0, 1) == ",") {
(void) nextToken();
continue;
}

break;
}

if (peekToken().substr(0, 1) == "[")
(void) nextToken();

if (peekToken().substr(0, 1) == "\"") {
std::string extraComment = nextToken().substr(1);

if (extraComment.size() > 1)
extraComment.pop_back();

annotation.extraComment = extraComment;
}

return true;
}

polyspace::CommentKind polyspace::Parser::parseKind()
{
const std::string token = nextToken();

if (token == "polyspace")
return CommentKind::Regular;

if (token == "polyspace-begin")
return CommentKind::Begin;

if (token == "polyspace-end")
return CommentKind::End;

return CommentKind::Invalid;
}

int polyspace::Parser::parseRange()
{
if (peekToken()[0] == '+') {
try {
const int range = std::stoi(peekToken().substr(1));
(void) nextToken();
return range;
} catch (...) {
return -1;
}
}

return 0;
}

void polyspace::Parser::handleAnnotation(const polyspace::Annotation &annotation)
{
for (const auto &resultName : annotation.resultNames) {
Suppression suppr = {
annotation.family,
resultName,
annotation.filename,
annotation.extraComment,
0,
0,
};

switch (annotation.kind) {
case CommentKind::Regular:
{
suppr.lineBegin = annotation.line;
suppr.lineEnd = annotation.line + annotation.range;
mDone.push_back(suppr);
break;
}
case CommentKind::Begin:
{
suppr.lineBegin = annotation.line;
mStarted.push_back(suppr);
break;
}
case CommentKind::End:
{
auto it = std::find_if(
mStarted.begin(),
mStarted.end(),
[&] (const Suppression &other) {
return suppr.matches(other);
}
);

if (it == mStarted.end())
break;

suppr.lineBegin = it->lineBegin;
suppr.lineEnd = annotation.line;
mStarted.erase(it);
mDone.push_back(suppr);
break;
}
case CommentKind::Invalid:
{
assert(false); // Invalid comments are not handled
}
}
}
}

void polyspace::Parser::collect(SuppressionList &suppressions) const
{
for (const auto &polyspaceSuppr : mDone) {
const auto it = mFamilyMap.find(polyspaceSuppr.family);
if (it == mFamilyMap.cend())
continue;

SuppressionList::Suppression suppr;
suppr.errorId = it->second + polyspaceSuppr.resultName;
suppr.isInline = true;
suppr.isPolyspace = true;
suppr.fileName = polyspaceSuppr.filename;
suppr.extraComment = polyspaceSuppr.extraComment;

suppr.lineNumber = polyspaceSuppr.lineBegin;
if (polyspaceSuppr.lineBegin == polyspaceSuppr.lineEnd) {
suppr.type = SuppressionList::Type::unique;
} else {
suppr.type = SuppressionList::Type::block;
suppr.lineBegin = polyspaceSuppr.lineBegin;
suppr.lineEnd = polyspaceSuppr.lineEnd;
}

suppressions.addSuppression(std::move(suppr));
}
}

void polyspace::Parser::parse(const std::string &comment, int line, const std::string &filename)
{
if (mFamilyMap.empty())
return;

mComment = comment.substr(2);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My spontanous feeling about this parsing is that looks cludgy to me. mComment is assigned here in parse and tokens are grabbed from it in the nextToken(). I have the feeling I would prefer that we manipulate comment locally on the stack instead.. which makes the interface reentrant however reentrancy is not really my ultimate reason why I want this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I don't see the benefit of this. mComment is only modified within the call tree of parse, so there's no way to tamper with it. I think it simplifies the code to not pass it as a reference to all the other parsing functions.

mHasPeeked = false;

while (true) {
const CommentKind kind = parseKind();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this handle multiple polyspace suppressions in a comment? can't there only be one suppression in a comment?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There can be multiple: e7afafd

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From https://se.mathworks.com/help/bugfinder/ug/annotate-hide-known-acceptable-polyspace-results-web-browser.html:

/* polyspace Family_1 : Result_1_name "comment 1" polyspace Family_2 : Result_2_name "comment 2"*/

if (kind == CommentKind::Invalid)
return;

const int range = parseRange();
if (range < 0)
return;

Annotation annotation;
annotation.filename = filename;
annotation.kind = kind;
annotation.line = line;
annotation.range = range;

while (parseAnnotation(annotation)) {
handleAnnotation(annotation);
if (!annotation.extraComment.empty())
break;
}
}
}

bool polyspace::isPolyspaceComment(const std::string &comment)
{
const std::string polyspace = "polyspace";
const std::string::size_type pos = comment.find_first_not_of("/* ");
if (pos == std::string::npos)
return false;
return comment.compare(pos, polyspace.size(), polyspace, 0, polyspace.size()) == 0;
}

bool polyspace::Suppression::matches(const polyspace::Suppression &other) const
{
return family == other.family && resultName == other.resultName;
}
Loading
Loading