diff --git a/Makefile b/Makefile index 3fef3103d96..59524a37779 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/cli/cppcheckexecutor.cpp b/cli/cppcheckexecutor.cpp index c5daf5079e9..c69a4a9f56e 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -329,7 +329,11 @@ static bool reportUnmatchedSuppressions(const std::listnext) { if (!tok->comment) { onlyComments = false; continue; } + if (polyspace::isPolyspaceComment(tok->str())) { + polyspaceParser.parse(tok->str(), tok->location.line, tokens.file(tok->location)); + continue; + } + std::list inlineSuppressions; if (!parseInlineSuppressionCommentToken(tokens, tok, inlineSuppressions, bad)) continue; @@ -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) diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index b91db635478..d553f6ab0eb 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -26,6 +26,7 @@ #include "token.h" #include "tokenize.h" #include "tokenlist.h" +#include "settings.h" #include #include // std::isdigit, std::isalnum, etc @@ -647,3 +648,264 @@ std::string SuppressionList::Suppression::toString() const } return s; } + +std::string polyspace::Parser::peekToken() +{ + if (!mHasPeeked) { + mPeeked = nextToken(); + mHasPeeked = true; + } + return mPeeked; +} + +std::string polyspace::Parser::nextToken() +{ + if (mHasPeeked) { + mHasPeeked = false; + return mPeeked; + } + + if (mComment.compare(0, 2, "/*") == 0 || mComment.compare(0, 2, "//") == 0) + mComment = mComment.substr(2); + + 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; +} + +void polyspace::Parser::finishSuppression() +{ + Suppression suppr = { mFamily, mResultName, mFilename, 0, 0 }; + + switch (mKind) { + case CommentKind::Regular: + { + suppr.lineBegin = mLine; + suppr.lineEnd = mLine + mRange; + mDone.push_back(suppr); + return; + } + case CommentKind::Begin: + { + suppr.lineBegin = mLine; + mStarted.push_back(suppr); + return; + } + case CommentKind::End: + { + auto it = std::find_if( + mStarted.begin(), + mStarted.end(), + [&] (const Suppression &other) { + return suppr.matches(other); + } + ); + + if (it == mStarted.end()) + return; + + suppr.lineBegin = it->lineBegin; + suppr.lineEnd = mLine; + mStarted.erase(it); + mDone.push_back(suppr); + return; + } + } +} + +bool polyspace::Parser::parseEntry() +{ + mFamily = nextToken(); + if (mFamily.empty()) + return false; + + if (nextToken() != ":") + return false; + + // Parse result name, multiple names may be separated by commas + while (!mComment.empty()) { + mResultName = nextToken(); + if (mResultName.empty()) + return false; + + finishSuppression(); + + if (peekToken() == ",") { + (void) nextToken(); + continue; + } + + break; + } + + // Skip status and severity + if (!peekToken().empty() && mPeeked[0] == '[') + (void) nextToken(); + + return true; +} + +void polyspace::Parser::collect(SuppressionList &suppressions) const +{ + for (const auto &polyspaceSuppr : mDone) { + SuppressionList::Suppression suppr; + if (polyspaceSuppr.convert(mSettings, suppr)) + suppressions.addSuppression(std::move(suppr)); + } +} + +void polyspace::Parser::parse(const std::string &comment, int line, const std::string &filename) +{ + mComment = comment; + mLine = line; + mFilename = filename; + mHasPeeked = false; + + while (true) { + const std::string kindStr = nextToken(); + if (kindStr.empty()) + return; + + if (kindStr == "polyspace") mKind = CommentKind::Regular; + else if (kindStr == "polyspace-begin") mKind = CommentKind::Begin; + else if (kindStr == "polyspace-end") mKind = CommentKind::End; + else return; + + mRange = 0; + if (peekToken()[0] == '+') { + try { mRange = std::stoi(mPeeked.substr(1)); } catch (...) { return; } + (void) nextToken(); + } + + while (parseEntry()) { + if (peekToken().empty() || mPeeked[0] == '\"') + break; + } + + if (!peekToken().empty() && mPeeked[0] == '\"') { + (void) nextToken(); + if (peekToken().empty()) + return; + continue; + } + + 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; +} + +bool polyspace::Suppression::convert(const Settings &settings, SuppressionList::Suppression &suppr) const +{ + static const std::map map = { + { "MISRA-C-2023", "premium-misra-c-2023-" }, + { "MISRA-CPP", "premium-misra-cpp-2008-" }, + { "MISRA-CPP-2023", "premium-misra-cpp-2023-" }, + { "CERT-C", "premium-cert-c-" }, + { "CERT-CPP", "premium-cert-cpp-" }, + { "AUTOSAR-CPP14", "premium-autosar-" }, + }; + + const auto it = map.find(family); + std::string prefix; + if (it == map.cend()) { + if (family == "MISRA-C3" || family == "MISRA2012") { + if (settings.premiumArgs.empty()) { + prefix = "misra-c2012-"; + } else { + prefix = "premium-misra-c-2012-"; + } + } else { + return false; + } + } else { + prefix = it->second; + } + + suppr.errorId = prefix + resultName; + suppr.isInline = true; + suppr.isPolyspace = true; + suppr.fileName = filename; + + suppr.lineNumber = lineBegin; + if (lineBegin == lineEnd) { + suppr.type = SuppressionList::Type::unique; + } else { + suppr.type = SuppressionList::Type::block; + suppr.lineBegin = lineBegin; + suppr.lineEnd = lineEnd; + } + + return true; +} diff --git a/lib/suppressions.h b/lib/suppressions.h index 022a492c51f..b891568632c 100644 --- a/lib/suppressions.h +++ b/lib/suppressions.h @@ -36,6 +36,7 @@ class Tokenizer; class ErrorMessage; enum class Certainty : std::uint8_t; class FileWithDetails; +class Settings; /// @addtogroup Core /// @{ @@ -160,6 +161,7 @@ class CPPCHECKLIB SuppressionList { 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{}; + bool isPolyspace{}; enum : std::int8_t { NO_LINE = -1 }; }; @@ -294,6 +296,54 @@ struct Suppressions SuppressionList nofail; }; +namespace polyspace { + + struct CPPCHECKLIB Suppression { + std::string family; + std::string resultName; + std::string filename; + int lineBegin; + int lineEnd; + + bool matches(const Suppression &other) const; + bool convert(const Settings &settings, SuppressionList::Suppression &suppr) const; + }; + + class CPPCHECKLIB Parser { + public: + Parser() = delete; + explicit Parser(const Settings &settings) : mSettings(settings) {} + void collect(SuppressionList &suppressions) const; + void parse(const std::string &comment, int line, const std::string &filename); + + private: + std::string peekToken(); + std::string nextToken(); + void finishSuppression(); + bool parseEntry(); + + enum class CommentKind : std::uint8_t { + Regular, Begin, End, + }; + + std::list mStarted; + std::list mDone; + std::string mComment; + std::string mFilename; + int mLine{}; + int mRange{}; + CommentKind mKind{}; + std::string mFamily; + std::string mResultName; + std::string mPeeked; + bool mHasPeeked{}; + const Settings &mSettings; + }; + + bool CPPCHECKLIB isPolyspaceComment(const std::string &comment); + +} + /// @} //--------------------------------------------------------------------------- #endif // suppressionsH diff --git a/oss-fuzz/Makefile b/oss-fuzz/Makefile index ad7fb641024..43f093810da 100644 --- a/oss-fuzz/Makefile +++ b/oss-fuzz/Makefile @@ -332,7 +332,7 @@ $(libcppdir)/standards.o: ../lib/standards.cpp ../externals/simplecpp/simplecpp. $(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) ${LIB_FUZZING_ENGINE} $(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) ${LIB_FUZZING_ENGINE} $(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 diff --git a/test/testsuppressions.cpp b/test/testsuppressions.cpp index 1f572bd2eb2..8758c0167ad 100644 --- a/test/testsuppressions.cpp +++ b/test/testsuppressions.cpp @@ -116,6 +116,21 @@ class TestSuppressions : public TestFixture { TEST_CASE(suppressionFromErrorMessage); TEST_CASE(suppressionWildcard); + + TEST_CASE(polyspaceMisraC2012); + TEST_CASE(polyspacePremiumMisraC2012); + TEST_CASE(polyspaceMisraC2023); + TEST_CASE(polyspaceMisraCpp2008); + TEST_CASE(polyspaceMisraCpp2023); + TEST_CASE(polyspaceCertC); + TEST_CASE(polyspaceCertCpp); + TEST_CASE(polyspaceAutosar); + TEST_CASE(polyspaceIgnored); + TEST_CASE(polyspaceMultiple1); + TEST_CASE(polyspaceMultiple2); + TEST_CASE(polyspaceMultiple3); + TEST_CASE(polyspaceRange); + TEST_CASE(polyspaceBlock); } void suppressionsBadId1() const { @@ -1896,6 +1911,198 @@ class TestSuppressions : public TestFixture { ASSERT(!suppressions.getUnmatchedGlobalSuppressions().empty()); } } + + void polyspaceMisraC2012() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("/* polyspace MISRA2012 : 2.7 */", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT(s->isInline); + ASSERT(s->isPolyspace); + ASSERT_EQUALS("misra-c2012-2.7", s->errorId); + ASSERT_EQUALS(1, s->lineNumber); + ASSERT_EQUALS_ENUM(SuppressionList::Type::unique, s->type); + ASSERT_EQUALS(SuppressionList::Suppression::NO_LINE, s->lineBegin); + ASSERT_EQUALS(SuppressionList::Suppression::NO_LINE, s->lineEnd); + } + + void polyspacePremiumMisraC2012() const { + SuppressionList list; + Settings settings; + settings.premiumArgs = "--misra-c-2012"; + polyspace::Parser parser(settings); + parser.parse("/* polyspace MISRA2012 : 2.7 */", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT_EQUALS("premium-misra-c-2012-2.7", s->errorId); + } + + void polyspaceMisraC2023() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("// polyspace MISRA-C-2023 : *", 2, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT_EQUALS("premium-misra-c-2023-*", s->errorId); + ASSERT_EQUALS(2, s->lineNumber); + ASSERT_EQUALS(SuppressionList::Suppression::NO_LINE, s->lineBegin); + ASSERT_EQUALS(SuppressionList::Suppression::NO_LINE, s->lineEnd); + } + + void polyspaceMisraCpp2008() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("// polyspace MISRA-CPP : 7-1-1", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT_EQUALS("premium-misra-cpp-2008-7-1-1", s->errorId); + } + + void polyspaceMisraCpp2023() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("// polyspace MISRA-CPP-2023 : 4.6.1", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT_EQUALS("premium-misra-cpp-2023-4.6.1", s->errorId); + } + + void polyspaceCertC() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("// polyspace CERT-C : PRE30", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT_EQUALS("premium-cert-c-PRE30", s->errorId); + } + + void polyspaceCertCpp() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("// polyspace CERT-CPP : CTR51", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT_EQUALS("premium-cert-cpp-CTR51", s->errorId); + } + + void polyspaceAutosar() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("// polyspace AUTOSAR-CPP14 : a2-10-1", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT_EQUALS("premium-autosar-a2-10-1", s->errorId); + } + + void polyspaceIgnored() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("// polyspace DEFECT : INT_OVFL", 1, "file.c"); + parser.collect(list); + ASSERT(list.getSuppressions().empty()); + } + + void polyspaceMultiple1() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("/* polyspace MISRA2012 : 2.7, 9.1 */", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(2, supprs.size()); + auto s = supprs.cbegin(); + ASSERT_EQUALS("misra-c2012-2.7", s->errorId); + s++; + ASSERT_EQUALS("misra-c2012-9.1", s->errorId); + } + + void polyspaceMultiple2() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("/* polyspace MISRA2012 : 2.7 MISRA-CPP : 7-1-1 */", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(2, supprs.size()); + auto s = supprs.cbegin(); + ASSERT_EQUALS("misra-c2012-2.7", s->errorId); + s++; + ASSERT_EQUALS("premium-misra-cpp-2008-7-1-1", s->errorId); + } + + void polyspaceMultiple3() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("/* polyspace MISRA2012 : 2.7 [Justified:Low] \"comment 1\" polyspace MISRA-CPP : 7-1-1 \"comment 2\"*/", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(2, supprs.size()); + auto s = supprs.cbegin(); + ASSERT_EQUALS("misra-c2012-2.7", s->errorId); + s++; + ASSERT_EQUALS("premium-misra-cpp-2008-7-1-1", s->errorId); + } + + void polyspaceRange() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("/* polyspace +3 MISRA2012 : 2.7 */", 1, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT(s->isInline); + ASSERT_EQUALS("misra-c2012-2.7", s->errorId); + ASSERT_EQUALS(1, s->lineNumber); + ASSERT_EQUALS_ENUM(SuppressionList::Type::block, s->type); + ASSERT_EQUALS(1, s->lineBegin); + ASSERT_EQUALS(4, s->lineEnd); + } + + void polyspaceBlock() const { + SuppressionList list; + const Settings settings; + polyspace::Parser parser(settings); + parser.parse("/* polyspace-begin MISRA2012 : 2.7 */", 1, "file.c"); + parser.parse("/* polyspace-end MISRA2012 : 2.7 */", 5, "file.c"); + parser.collect(list); + const auto &supprs = list.getSuppressions(); + ASSERT_EQUALS(1, supprs.size()); + const auto s = supprs.cbegin(); + ASSERT(s->isInline); + ASSERT_EQUALS("misra-c2012-2.7", s->errorId); + ASSERT_EQUALS(1, s->lineNumber); + ASSERT_EQUALS_ENUM(SuppressionList::Type::block, s->type); + ASSERT_EQUALS(1, s->lineBegin); + ASSERT_EQUALS(5, s->lineEnd); + } }; REGISTER_TEST(TestSuppressions)