From dcaff4f49c19795976054852d622ffbd10260b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Thu, 29 Jan 2026 11:16:16 +0100 Subject: [PATCH 1/2] fix 14367 --- Makefile | 2 +- cli/cppcheckexecutor.cpp | 3 +- lib/preprocessor.cpp | 35 ++- lib/suppressions.cpp | 261 ++++++++++++++++++- lib/suppressions.h | 31 +++ oss-fuzz/Makefile | 2 +- test/cli/inline-suppress-polyspace_test.py | 63 +++++ test/testsuppressions.cpp | 288 ++++++++++++++++++++- 8 files changed, 662 insertions(+), 23 deletions(-) create mode 100644 test/cli/inline-suppress-polyspace_test.py diff --git a/Makefile b/Makefile index 753348c618a..7cfa6816af9 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 a16f76183c0..716478f0baf 100644 --- a/cli/cppcheckexecutor.cpp +++ b/cli/cppcheckexecutor.cpp @@ -329,7 +329,8 @@ static bool reportUnmatchedSuppressions(const std::listnext) { if (!tok->comment) { onlyComments = false; @@ -207,20 +209,24 @@ static void addInlineSuppressions(const simplecpp::TokenList &tokens, const Sett } std::list inlineSuppressions; - if (!parseInlineSuppressionCommentToken(tokens, tok, inlineSuppressions, bad)) - continue; + if (polyspace::isPolyspaceComment(tok->str())) { + inlineSuppressions = polyspaceParser.parse(tok->str(), tok->location.line, getRelativeFilename(tokens, tok, settings)); + } else { + if (!parseInlineSuppressionCommentToken(tokens, tok, inlineSuppressions, bad)) + continue; - if (!sameline(tok->previous, tok)) { - // find code after comment.. - if (tok->next) { - tok = tok->next; + if (!sameline(tok->previous, tok)) { + // find code after comment.. + if (tok->next) { + tok = tok->next; - while (tok->comment) { - parseInlineSuppressionCommentToken(tokens, tok, inlineSuppressions, bad); - if (tok->next) { - tok = tok->next; - } else { - break; + while (tok->comment) { + parseInlineSuppressionCommentToken(tokens, tok, inlineSuppressions, bad); + if (tok->next) { + tok = tok->next; + } else { + break; + } } } } @@ -249,8 +255,9 @@ static void addInlineSuppressions(const simplecpp::TokenList &tokens, const Sett for (SuppressionList::Suppression &suppr : inlineSuppressions) { suppr.fileName = relativeFilename; - if (SuppressionList::Type::blockBegin == suppr.type) - { + if (SuppressionList::Type::block == suppr.type) { + suppressions.addSuppression(std::move(suppr)); + } else if (SuppressionList::Type::blockBegin == suppr.type) { inlineSuppressionsBlockBegin.push_back(std::move(suppr)); } else if (SuppressionList::Type::blockEnd == suppr.type) { bool throwError = true; diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index b91db635478..0aa0f6f1449 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 @@ -244,6 +245,15 @@ SuppressionList::Suppression SuppressionList::parseLine(const std::string &line) suppression.fileName.erase(pos); } } + + // when parsing string generated internally by toString() there can be newline + std::string extra; + while (std::getline(lineStream, extra)) { + if (startsWith(extra, "symbol=")) + suppression.symbolName = extra.substr(7); + else if (extra == "polyspace=1") + suppression.isPolyspace = true; + } } } @@ -632,7 +642,7 @@ void SuppressionList::markUnmatchedInlineSuppressionsAsChecked(const Tokenizer & std::string SuppressionList::Suppression::toString() const { std::string s; - s+= errorId; + s += errorId; if (!fileName.empty()) { s += ':'; s += fileName; @@ -641,9 +651,250 @@ std::string SuppressionList::Suppression::toString() const s += std::to_string(lineNumber); } } - if (!symbolName.empty()) { - s += ':'; - s += symbolName; - } + if (!symbolName.empty()) + s += "\nsymbol=" + symbolName; + if (isPolyspace) + s += "\npolyspace=1"; return s; } + +polyspace::Parser::Parser(const Settings &settings) +{ + const bool 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; + + const char prevChar = (pos > 0) ? args[pos - 1] : ' '; + const char nextChar = (pos + arg.size() < args.size()) ? args[pos + arg.size()] : ' '; + + return prevChar == ' ' && (nextChar == ' ' || nextChar == ':'); + }; + + 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-"; +} + +polyspace::CommentKind polyspace::Parser::parseKind(const std::string& comment, std::string::size_type& pos) +{ + const std::string::size_type pos1 = pos; + pos = comment.find_first_of(" \t", pos); + if (pos >= comment.size()) + return CommentKind::Invalid; + + const std::string token = comment.substr(pos1, pos-pos1); + + if (token == "polyspace") + return CommentKind::Regular; + + if (token == "polyspace-begin") + return CommentKind::Begin; + + if (token == "polyspace-end") + return CommentKind::End; + + return CommentKind::Invalid; +} + + +std::list polyspace::Parser::parse(const std::string &comment, int line, const std::string &filename) +{ + // Syntax for a polyspace suppression: + // https://se.mathworks.com/help/bugfinder/ug/annotate-hide-known-acceptable-polyspace-results-web-browser.html + + std::list ret; + + if (mFamilyMap.empty()) + return ret; + + for (std::string::size_type pos = comment.find_first_not_of("/* "); pos < comment.size();) { + // polyspace + const auto polyspaceKind = parseKind(comment, pos); + if (polyspaceKind == CommentKind::Invalid) + break; + + // optional range + const int rangeValue = parseRange(comment, pos); + + // ids.. + const std::set ids = parseIds(comment, pos); + + // skip justification + if (pos < comment.size() && comment[pos] == '[') { + pos = comment.find(']',pos+1); + if (pos >= comment.size()) + break; + pos = comment.find_first_not_of(" \t", pos+1); + if (pos >= comment.size()) + break; + } + + // extra comment + std::string extraComment; + if (pos < comment.size() && comment[pos] == '\"') { + const std::string::size_type p1 = pos + 1; + pos = comment.find('\"',p1); + if (pos >= comment.size()) + break; + extraComment = comment.substr(p1, pos-p1); + } + + for (const std::string& errorId: ids) { + SuppressionList::Suppression suppr; + suppr.errorId = errorId; + suppr.isInline = true; + suppr.isPolyspace = true; + suppr.fileName = filename; + suppr.lineNumber = line; + suppr.extraComment = extraComment; + + if (rangeValue > 0) { + suppr.type = SuppressionList::Type::block; + suppr.lineBegin = line; + suppr.lineEnd = line + rangeValue; + } + else if (polyspaceKind == polyspace::CommentKind::Regular) + suppr.type = SuppressionList::Type::unique; + else if (polyspaceKind == polyspace::CommentKind::Begin) + suppr.type = SuppressionList::Type::blockBegin; + else + suppr.type = SuppressionList::Type::blockEnd; + + ret.emplace_back(suppr); + } + + // proceed to next "polyspace" if it exists + if (pos < comment.size()) + pos = comment.find("polyspace", pos); + } + + return ret; +} + + +int polyspace::Parser::parseRange(const std::string& comment, std::string::size_type& pos) { + pos = comment.find_first_not_of(" \t", pos); + if (pos >= comment.size()) + return 0; + if (comment[pos] != '+') + return 0; + const std::string::size_type startpos = pos + 1; + std::string::size_type endpos = comment.find_first_of(" \t", startpos); + if (endpos > comment.size()) + return 0; + const std::string range = comment.substr(startpos, endpos-startpos); + try { + int ret = std::stoi(range); + pos = endpos; + return ret; + } catch (const std::invalid_argument &) {} + return 0; +} + +std::vector> polyspace::Parser::parseFamilyRules(const std::string& comment, std::string::size_type& pos) { + std::vector> fr; + std::string family; + std::string rule; + enum class State: uint8_t { family, colon, rule, rule_or_family } state = State::family; + const std::string::size_type endpos = startsWith(comment, "/*") ? comment.size() - 2 : comment.size(); + for (; pos <= endpos; ++pos) { + const char c = comment[pos]; + if (std::strchr("[\"", c)) + break; + switch (state) { + case State::family: + if (std::isalnum(c) || std::strchr("-_.",c)) + family += c; + else if (!family.empty() && std::strchr(" \t:",c)) + state = State::colon; + break; + case State::colon: + if (!std::strchr(" \t:", c)) { + rule.clear(); + --pos; + state = State::rule; + } + break; + case State::rule: + if (std::strchr(", \t",c)) { + if (!rule.empty()) { + fr.emplace_back(family,rule); + rule.clear(); + if (c != ',') + state = State::rule_or_family; + } + } + else + rule += c; + break; + case State::rule_or_family: + rule.clear(); + if (std::isalnum(c)) { + --pos; + family.clear(); + state = State::family; + } else if (c == ',') { + --pos; + state = State::rule; + } + break; + } + } + if (!family.empty() && !rule.empty()) + fr.emplace_back(family,rule); + return fr; +} + +std::set polyspace::Parser::parseIds(const std::string& comment, std::string::size_type& pos) const { + std::set ids; + for (const auto& fr: parseFamilyRules(comment,pos)) { + const auto it = mFamilyMap.find(fr.first); + if (it != mFamilyMap.cend()) + ids.emplace(it->second + fr.second); + } + return ids; +} + + +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; +} diff --git a/lib/suppressions.h b/lib/suppressions.h index 022a492c51f..b1eb02cb18e 100644 --- a/lib/suppressions.h +++ b/lib/suppressions.h @@ -31,11 +31,13 @@ #include #include #include +#include class Tokenizer; class ErrorMessage; enum class Certainty : std::uint8_t; class FileWithDetails; +class Settings; /// @addtogroup Core /// @{ @@ -160,6 +162,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 +297,34 @@ struct Suppressions SuppressionList nofail; }; +namespace polyspace { + + enum class CommentKind : std::uint8_t { + Invalid, Regular, Begin, End, + }; + + class CPPCHECKLIB Parser { + public: + Parser() = delete; + explicit Parser(const Settings &settings); + + std::list parse(const std::string &comment, int line, const std::string &filename); + + static int parseRange(const std::string& comment, std::string::size_type& pos); + static std::vector> parseFamilyRules(const std::string& comment, std::string::size_type& pos); + + private: + std::set parseIds(const std::string& comment, std::string::size_type& pos) const; + + static CommentKind parseKind(const std::string& comment, std::string::size_type& pos); + + std::map mFamilyMap; + }; + + 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/cli/inline-suppress-polyspace_test.py b/test/cli/inline-suppress-polyspace_test.py new file mode 100644 index 00000000000..889bf231ef3 --- /dev/null +++ b/test/cli/inline-suppress-polyspace_test.py @@ -0,0 +1,63 @@ + +# python -m pytest inline-suppress-polyspace_test.py + +import os +from testutils import assert_cppcheck + +__script_dir = os.path.dirname(os.path.abspath(__file__)) + + +def test_unmatched_polyspace_suppression(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, 'wt') as f: + f.write('int f(void); /* polyspace MISRA2012:8.2 */\n') + + args = ['--addon=misra', '--template=simple', '--enable=style,information', '--inline-suppr', 'test.c'] + + out_exp = ['Checking test.c ...'] + err_exp = ['test.c:1:0: information: Unmatched suppression: misra-c2012-8.2 [unmatchedPolyspaceSuppression]'] + + assert_cppcheck(args, ec_exp=0, err_exp=err_exp, out_exp=out_exp, cwd=str(tmp_path)) + + +def test_1(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, 'wt') as f: + f.write('int f(); /* polyspace MISRA2012:8.2 */\n') + + args = ['--addon=misra', '--template=simple', '--enable=style,information', '--inline-suppr', 'test.c'] + + out_exp = ['Checking test.c ...'] + err_exp = [] + + assert_cppcheck(args, ec_exp=0, err_exp=err_exp, out_exp=out_exp, cwd=str(tmp_path)) + + +def test_block(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, 'wt') as f: + f.write('/* polyspace +1 MISRA2012:8.2 */\n' + 'int f();\n' # <- suppression applies to this line + 'int g();\n') # <- suppression does not apply to this line + + args = ['--addon=misra', '--template=simple', '--enable=style,information', '--inline-suppr', 'test.c'] + + out_exp = ['Checking test.c ...'] + err_exp = ['test.c:3:6: style: misra violation (use --rule-texts= to get proper output) [misra-c2012-8.2]'] + + assert_cppcheck(args, ec_exp=0, err_exp=err_exp, out_exp=out_exp, cwd=str(tmp_path)) + + +def test_begin_end(tmp_path): + test_file = tmp_path / 'test.c' + with open(test_file, 'wt') as f: + f.write('/* polyspace-begin MISRA2012:8.2 */\n' + 'int f();\n' + '/* polyspace-end MISRA2012:8.2 */\n') + + args = ['--addon=misra', '--template=simple', '--enable=style,information', '--inline-suppr', 'test.c'] + + out_exp = ['Checking test.c ...'] + err_exp = [] + + assert_cppcheck(args, ec_exp=0, err_exp=err_exp, out_exp=out_exp, cwd=str(tmp_path)) diff --git a/test/testsuppressions.cpp b/test/testsuppressions.cpp index 61bb0f9cc1a..e8ebc4c4702 100644 --- a/test/testsuppressions.cpp +++ b/test/testsuppressions.cpp @@ -55,6 +55,7 @@ class TestSuppressions : public TestFixture { void run() override { mNewTemplate = true; + TEST_CASE(parseLine); TEST_CASE(suppressionsBadId1); TEST_CASE(suppressionsDosFormat); // Ticket #1836 TEST_CASE(suppressionsFileNameWithColon); // Ticket #1919 - filename includes colon @@ -120,6 +121,36 @@ class TestSuppressions : public TestFixture { TEST_CASE(suppressionFromErrorMessage); TEST_CASE(suppressionWildcard); + + TEST_CASE(polyspaceParseRange); + TEST_CASE(polyspaceParseIds); + + 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); + TEST_CASE(polyspaceExtraComments); + } + + + void parseLine() const { + ASSERT_EQUALS("bad:test.c:1", SuppressionList::parseLine("bad:test.c:1").toString()); + + // symbol + ASSERT_EQUALS("bad:test.c:1\nsymbol=x", SuppressionList::parseLine("bad:test.c:1\nsymbol=x").toString()); + + // polyspace + ASSERT_EQUALS("bad:test.c:1\npolyspace=1", SuppressionList::parseLine("bad:test.c:1\npolyspace=1").toString()); } void suppressionsBadId1() const { @@ -1791,7 +1822,7 @@ class TestSuppressions : public TestFixture { SuppressionList::Suppression s; s.errorId = "unitvar"; s.symbolName = "sym"; - ASSERT_EQUALS("unitvar:sym", s.toString()); + ASSERT_EQUALS("unitvar\nsymbol=sym", s.toString()); } } @@ -1900,6 +1931,261 @@ class TestSuppressions : public TestFixture { ASSERT(!suppressions.getUnmatchedGlobalSuppressions().empty()); } } + + static std::string polyspaceParseIdsResults(const std::string& comment, std::string::size_type pos) { + std::string ret; + for (const auto& fr: polyspace::Parser::parseFamilyRules(comment,pos)) { + if (!fr.second.empty()) + ret += ',' + fr.first + ':' + fr.second; + } + return ret.empty() ? ret : ret.substr(1); + } + + void polyspaceParseRange() const { + std::string::size_type pos; + + // Happy case + pos = 0; + ASSERT_EQUALS(12, polyspace::Parser::parseRange(" +12 ",pos)); + ASSERT_EQUALS(4U, pos); + + // Invalid range => pos will point at the token + pos = 0; + ASSERT_EQUALS(0, polyspace::Parser::parseRange(" ",pos)); + ASSERT_EQUALS(std::string::npos, pos); + + pos = 0; + ASSERT_EQUALS(0, polyspace::Parser::parseRange(" test ",pos)); + ASSERT_EQUALS(1U, pos); + + pos = 0; + ASSERT_EQUALS(0, polyspace::Parser::parseRange(" +",pos)); + ASSERT_EQUALS(1U, pos); + + pos = 0; + ASSERT_EQUALS(0, polyspace::Parser::parseRange(" +12",pos)); + ASSERT_EQUALS(1U, pos); + + pos = 0; + ASSERT_EQUALS(0, polyspace::Parser::parseRange(" +A ",pos)); + ASSERT_EQUALS(1U, pos); + } + + void polyspaceParseIds() const { + ASSERT_EQUALS("test:12", polyspaceParseIdsResults("abc test:12",3)); + ASSERT_EQUALS("test:12", polyspaceParseIdsResults("abc test:12 [12]",3)); + ASSERT_EQUALS("test:12", polyspaceParseIdsResults("abc test:12 */",3)); + ASSERT_EQUALS("test:1*", polyspaceParseIdsResults("abc test:1* */",3)); + ASSERT_EQUALS("test:*", polyspaceParseIdsResults("// abc test:*",6)); + // -_. + ASSERT_EQUALS("test:1-2.3", polyspaceParseIdsResults("abc test:1-2.3",3)); + ASSERT_EQUALS("t-e_s.t:12", polyspaceParseIdsResults("abc t-e_s.t : 12",3)); + // multiple ids + ASSERT_EQUALS("d:1,d:2", polyspaceParseIdsResults("abc d:1,2",3)); + ASSERT_EQUALS("d:1,d:2,e:3,e:4,f:6", polyspaceParseIdsResults("abc d:1,2 e: 3 , 4 f : 6 ",3)); + } + + struct PolyspaceComment { + std::string text; + int line; + + PolyspaceComment(const std::string &&text, int line) + : text(text) + , line(line) + {} + }; + + struct PolyspaceParseResult { + std::string errorId; + int lineNumber; + std::string extraComment; + SuppressionList::Type type = SuppressionList::Type::unique; + int lineBegin = SuppressionList::Suppression::NO_LINE; + int lineEnd = SuppressionList::Suppression::NO_LINE; + + PolyspaceParseResult(const std::string &&errorId, + int lineNumber, + const std::string &&extraComment = "", + SuppressionList::Type type = SuppressionList::Type::unique, + int lineBegin = SuppressionList::Suppression::NO_LINE, + int lineEnd = SuppressionList::Suppression::NO_LINE) + : errorId(errorId) + , lineNumber(lineNumber) + , extraComment(extraComment) + , type(type) + , lineBegin(lineBegin) + , lineEnd(lineEnd) + {} + }; + + void testPolyspaceSuppression(const std::string& addon, + const std::string& premiumArgs, + const PolyspaceComment& comment, + std::initializer_list results) const + { + SuppressionList list; + Settings settings; + if (!addon.empty()) { + AddonInfo info; + info.name = addon; + settings.addonInfos.push_back(info); + } + settings.premiumArgs = premiumArgs; + polyspace::Parser parser(settings); + + const std::string fileName = "file.c"; + const auto supprs = parser.parse(comment.text, comment.line, fileName); + + ASSERT_EQUALS(results.size(), supprs.size()); + + auto supprIt = supprs.cbegin(); + const auto *resultIt = results.begin(); + + for (; supprIt != supprs.cend(); supprIt++, resultIt++) { + ASSERT(supprIt->isPolyspace); + ASSERT(supprIt->isInline); + ASSERT_EQUALS(fileName, supprIt->fileName); + ASSERT_EQUALS(resultIt->errorId, supprIt->errorId); + ASSERT_EQUALS(resultIt->extraComment, supprIt->extraComment); + ASSERT_EQUALS_ENUM(resultIt->type, supprIt->type); + ASSERT_EQUALS(resultIt->lineNumber, supprIt->lineNumber); + ASSERT_EQUALS(resultIt->lineBegin, supprIt->lineBegin); + ASSERT_EQUALS(resultIt->lineEnd, supprIt->lineEnd); + } + } + + void polyspaceMisraC2012() const { + testPolyspaceSuppression( + "misra", "", + { "/* polyspace MISRA2012 : 2.7 */", 1 }, + { { "misra-c2012-2.7", 1 } } + ); + } + + void polyspacePremiumMisraC2012() const { + testPolyspaceSuppression( + "", "--misra-c-2012", + { "/* polyspace MISRA2012 : 2.7 */", 1 }, + { { "premium-misra-c-2012-2.7", 1 } } + ); + } + + void polyspaceMisraC2023() const { + testPolyspaceSuppression( + "", "--misra-c-2023", + { "// polyspace MISRA-C-2023 : *", 2 }, + { { "premium-misra-c-2023-*", 2 } } + ); + } + + void polyspaceMisraCpp2008() const { + testPolyspaceSuppression( + "", "--misra-cpp-2008", + { "// polyspace MISRA-CPP : 7-1-1", 1 }, + { { "premium-misra-cpp-2008-7-1-1", 1 } } + ); + } + + void polyspaceMisraCpp2023() const { + testPolyspaceSuppression( + "", "--misra-cpp-2023", + { "// polyspace MISRA-CPP-2023 : 4.6.1", 1 }, + { { "premium-misra-cpp-2023-4.6.1", 1 } } + ); + } + + void polyspaceCertC() const { + testPolyspaceSuppression( + "", "--cert-c", + { "// polyspace CERT-C : PRE30", 1 }, + { { "premium-cert-c-PRE30", 1 } } + ); + } + + void polyspaceCertCpp() const { + testPolyspaceSuppression( + "", "--cert-cpp", + { "// polyspace CERT-CPP : CTR51", 1 }, + { { "premium-cert-cpp-CTR51", 1 } } + ); + } + + void polyspaceAutosar() const { + testPolyspaceSuppression( + "", "--autosar", + { "// polyspace AUTOSAR-CPP14 : a2-10-1", 1 }, + { { "premium-autosar-a2-10-1", 1 } } + ); + } + + void polyspaceIgnored() const { + testPolyspaceSuppression( + "", "", + { "// polyspace DEFECT : INT_OVFL AUTOSAR-CPP14 : a2-10-1", 1 }, + {} + ); + } + + void polyspaceMultiple1() const { + testPolyspaceSuppression( + "", "--misra-c-2012", + { "/* polyspace MISRA2012 : 2.7, 9.1 */", 1 }, + { { "premium-misra-c-2012-2.7", 1 }, + { "premium-misra-c-2012-9.1", 1 } } + ); + } + + void polyspaceMultiple2() const { + testPolyspaceSuppression( + "", "--misra-c-2012 --misra-cpp-2008", + { "/* polyspace MISRA2012 : 2.7 MISRA-CPP : 7-1-1 */", 1 }, + { { "premium-misra-c-2012-2.7", 1 }, + { "premium-misra-cpp-2008-7-1-1", 1 } } + ); + } + + void polyspaceMultiple3() const { + testPolyspaceSuppression( + "", "--misra-c-2012 --misra-cpp-2008", + { "/* polyspace MISRA2012 : 2.7 [Justified:Low] \"comment 1\" polyspace MISRA-CPP : 7-1-1 \"comment 2\"*/", 1 }, + { { "premium-misra-c-2012-2.7", 1, "comment 1" }, + { "premium-misra-cpp-2008-7-1-1", 1, "comment 2" }, } + ); + } + + void polyspaceRange() const { + testPolyspaceSuppression( + "", "--misra-c-2012", + { "/* polyspace +3 MISRA2012 : 2.7 */", 1 }, + { { "premium-misra-c-2012-2.7", 1, "", SuppressionList::Type::block, 1, 4 } } + ); + } + + void polyspaceBlock() const { + testPolyspaceSuppression( + "", "--misra-c-2012", + { "/* polyspace-begin MISRA2012 : 2.7 */", 1 }, + { { "premium-misra-c-2012-2.7", 1, "", SuppressionList::Type::blockBegin } } + ); + + testPolyspaceSuppression( + "", "--misra-c-2012", + { "/* polyspace-end MISRA2012 : 2.7 */", 1 }, + { { "premium-misra-c-2012-2.7", 1, "", SuppressionList::Type::blockEnd } } + ); + + } + + void polyspaceExtraComments() const { + testPolyspaceSuppression( + "", "--misra-c-2012 --misra-cpp-2008", + { "/* polyspace MISRA2012 : 2.7 MISRA-CPP : 7-1-1 \"comment 1\" polyspace MISRA2012 : 8.1, 8.3 \"comment 2\" */", 1 }, + { { "premium-misra-c-2012-2.7", 1, "comment 1" }, + { "premium-misra-cpp-2008-7-1-1", 1, "comment 1" }, + { "premium-misra-c-2012-8.1", 1, "comment 2" }, + { "premium-misra-c-2012-8.3", 1, "comment 2" }, } + ); + } }; REGISTER_TEST(TestSuppressions) From 7bcca81dbc2184d6b92b8cc203991a7d7cc3a829 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Fri, 30 Jan 2026 19:42:21 +0100 Subject: [PATCH 2/2] const --- lib/suppressions.cpp | 2 +- lib/suppressions.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/suppressions.cpp b/lib/suppressions.cpp index 0aa0f6f1449..1115414c21b 100644 --- a/lib/suppressions.cpp +++ b/lib/suppressions.cpp @@ -731,7 +731,7 @@ polyspace::CommentKind polyspace::Parser::parseKind(const std::string& comment, } -std::list polyspace::Parser::parse(const std::string &comment, int line, const std::string &filename) +std::list polyspace::Parser::parse(const std::string &comment, int line, const std::string &filename) const { // Syntax for a polyspace suppression: // https://se.mathworks.com/help/bugfinder/ug/annotate-hide-known-acceptable-polyspace-results-web-browser.html diff --git a/lib/suppressions.h b/lib/suppressions.h index b1eb02cb18e..eb6bf0bbace 100644 --- a/lib/suppressions.h +++ b/lib/suppressions.h @@ -308,7 +308,7 @@ namespace polyspace { Parser() = delete; explicit Parser(const Settings &settings); - std::list parse(const std::string &comment, int line, const std::string &filename); + std::list parse(const std::string &comment, int line, const std::string &filename) const; static int parseRange(const std::string& comment, std::string::size_type& pos); static std::vector> parseFamilyRules(const std::string& comment, std::string::size_type& pos);