From c305f4a1a054fe9c08d02ebda293109dc7f1d9aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Mon, 13 Jan 2025 14:19:41 +0100 Subject: [PATCH 1/4] add message for expanded macro leading to invalid syntax --- lib/tokenize.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 1167befdc83..735d84b6fa9 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -8729,10 +8729,16 @@ void Tokenizer::findGarbageCode() const if (Token::Match(tok, "%num%|%bool%|%char%|%str% %num%|%bool%|%char%|%str%") && !Token::Match(tok, "%str% %str%")) syntaxError(tok); if (Token::Match(tok, "%num%|%bool%|%char%|%str% {|(")) { - if (tok->strAt(1) == "(") + if (tok->strAt(1) == "(") { + if (tok->isExpandedMacro() && mSettings.userDefines.empty()) { + throw InternalError(tok, "literal used as function. Macro '" + tok->getMacroName() + + "' expands to '" + tok->str() + +"'. Use -D" + tok->getMacroName() + + "=... to specify a value, or -U" + tok->getMacroName() + + " to undefine it.", InternalError::UNKNOWN_MACRO); + } syntaxError(tok); - else if (!(tok->tokType() == Token::Type::eString && Token::simpleMatch(tok->tokAt(-1), "extern")) && - !(tok->tokType() == Token::Type::eBoolean && cpp && Token::simpleMatch(tok->tokAt(-1), "requires"))) + } else if (!(tok->tokType() == Token::Type::eString && Token::simpleMatch(tok->tokAt(-1), "extern")) && + !(tok->tokType() == Token::Type::eBoolean && cpp && Token::simpleMatch(tok->tokAt(-1), "requires"))) syntaxError(tok); } if (Token::Match(tok, "( ) %num%|%bool%|%char%|%str%")) From e77ee6f1ebdf03a62d29452c54792246d612ee81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Thu, 16 Jan 2025 17:20:21 +0100 Subject: [PATCH 2/4] add tests --- test/testcppcheck.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test/testcppcheck.cpp b/test/testcppcheck.cpp index f71b09aca0c..ed4ad325037 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -61,6 +61,8 @@ class TestCppcheck : public TestFixture { TEST_CASE(getDumpFileContentsRawTokens); TEST_CASE(getDumpFileContentsLibrary); TEST_CASE(getClangFlagsIncludeFile); + TEST_CASE(invalidSyntaxUnknownMacro1); // #13378 + TEST_CASE(invalidSyntaxUnknownMacro2); // #13378 } void getErrorMessages() const { @@ -251,6 +253,46 @@ class TestCppcheck : public TestFixture { ASSERT_EQUALS("-x c --include 1.h ", cppcheck.getClangFlags(Standards::Language::C)); } + void invalidSyntaxUnknownMacro1() { // #13378 + ScopedFile file("test.c", + "void foo(void) {\n" + "#ifdef START\n" + " START();\n" + "#endif\n" + "}\n"); + ErrorLogger2 errorLogger; + CppCheck cppcheck(errorLogger, false, {}); + + ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(file.path()))); + errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& errmsg) { + return errmsg.id == "logChecker"; + }), errorLogger.errmsgs.end()); + ASSERT_EQUALS(1, errorLogger.errmsgs.size()); + ASSERT_EQUALS("literal used as function. Macro 'START' expands to '1'. Use -DSTART=... to specify a value, or -USTART to undefine it.", + errorLogger.errmsgs.cbegin()->shortMessage()); + ASSERT_EQUALS("unknownMacro", errorLogger.errmsgs.cbegin()->id); + } + + void invalidSyntaxUnknownMacro2() { // #13378 + ScopedFile file("test.c", + "void foo(void) {\n" + "#ifdef START\n" + " START();\n" + "#endif\n" + "}\n"); + ErrorLogger2 errorLogger; + CppCheck cppcheck(errorLogger, false, {}); + cppcheck.settings().userDefines = "START=1"; // cppcheck -DSTART ... + + ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(file.path()))); + errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& errmsg) { + return errmsg.id == "logChecker"; + }), errorLogger.errmsgs.end()); + ASSERT_EQUALS(1, errorLogger.errmsgs.size()); + ASSERT_EQUALS("syntax error", errorLogger.errmsgs.cbegin()->shortMessage()); + ASSERT_EQUALS("syntaxError", errorLogger.errmsgs.cbegin()->id); + } + // TODO: test suppressions // TODO: test all with FS }; From 48af2c941dc0105d93c90e2550a1b2bb3db2c09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Sat, 18 Jan 2025 15:31:28 +0100 Subject: [PATCH 3/4] fix warning --- test/testcppcheck.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/testcppcheck.cpp b/test/testcppcheck.cpp index ed4ad325037..ad4c6198385 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -264,8 +264,8 @@ class TestCppcheck : public TestFixture { CppCheck cppcheck(errorLogger, false, {}); ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(file.path()))); - errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& errmsg) { - return errmsg.id == "logChecker"; + errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) { + return msg.id == "logChecker"; }), errorLogger.errmsgs.end()); ASSERT_EQUALS(1, errorLogger.errmsgs.size()); ASSERT_EQUALS("literal used as function. Macro 'START' expands to '1'. Use -DSTART=... to specify a value, or -USTART to undefine it.", @@ -285,8 +285,8 @@ class TestCppcheck : public TestFixture { cppcheck.settings().userDefines = "START=1"; // cppcheck -DSTART ... ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(file.path()))); - errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& errmsg) { - return errmsg.id == "logChecker"; + errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) { + return msg.id == "logChecker"; }), errorLogger.errmsgs.end()); ASSERT_EQUALS(1, errorLogger.errmsgs.size()); ASSERT_EQUALS("syntax error", errorLogger.errmsgs.cbegin()->shortMessage()); From 305aa7b976398e8c31701f4f11c20e574df8f8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludvig=20Gunne=20Lindstr=C3=B6m?= Date: Tue, 28 Jan 2025 13:03:36 +0100 Subject: [PATCH 4/4] add similar message for member acces + test --- lib/tokenize.cpp | 29 ++++++++++++++++++----------- lib/tokenize.h | 4 ++++ test/testcppcheck.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/lib/tokenize.cpp b/lib/tokenize.cpp index 735d84b6fa9..ee300db61d8 100644 --- a/lib/tokenize.cpp +++ b/lib/tokenize.cpp @@ -8113,6 +8113,19 @@ void Tokenizer::unknownMacroError(const Token *tok1) const throw InternalError(tok1, "There is an unknown macro here somewhere. Configuration is required. If " + tok1->str() + " is a macro then please configure it.", InternalError::UNKNOWN_MACRO); } +void Tokenizer::unknownMacroSyntaxError(const Token *tok, const std::string &description, + const Token *errTok, const std::string &code) const +{ + if (tok->isExpandedMacro() && mSettings.userDefines.empty()) { + throw InternalError(tok, description + ". Macro '" + tok->getMacroName() + + "' expands to '" + tok->str() + +"'. Use -D" + tok->getMacroName() + + "=... to specify a value, or -U" + tok->getMacroName() + + " to undefine it.", InternalError::UNKNOWN_MACRO); + } + errTok = errTok ? errTok : tok; + syntaxError(errTok, code); +} + void Tokenizer::unhandled_macro_class_x_y(const Token *tok, const std::string& type, const std::string& x, const std::string& y, const std::string& bracket) const { reportError(tok, @@ -8729,16 +8742,10 @@ void Tokenizer::findGarbageCode() const if (Token::Match(tok, "%num%|%bool%|%char%|%str% %num%|%bool%|%char%|%str%") && !Token::Match(tok, "%str% %str%")) syntaxError(tok); if (Token::Match(tok, "%num%|%bool%|%char%|%str% {|(")) { - if (tok->strAt(1) == "(") { - if (tok->isExpandedMacro() && mSettings.userDefines.empty()) { - throw InternalError(tok, "literal used as function. Macro '" + tok->getMacroName() + - "' expands to '" + tok->str() + +"'. Use -D" + tok->getMacroName() + - "=... to specify a value, or -U" + tok->getMacroName() - + " to undefine it.", InternalError::UNKNOWN_MACRO); - } - syntaxError(tok); - } else if (!(tok->tokType() == Token::Type::eString && Token::simpleMatch(tok->tokAt(-1), "extern")) && - !(tok->tokType() == Token::Type::eBoolean && cpp && Token::simpleMatch(tok->tokAt(-1), "requires"))) + if (tok->strAt(1) == "(") + unknownMacroSyntaxError(tok, "literal used as function"); + else if (!(tok->tokType() == Token::Type::eString && Token::simpleMatch(tok->tokAt(-1), "extern")) && + !(tok->tokType() == Token::Type::eBoolean && cpp && Token::simpleMatch(tok->tokAt(-1), "requires"))) syntaxError(tok); } if (Token::Match(tok, "( ) %num%|%bool%|%char%|%str%")) @@ -8774,7 +8781,7 @@ void Tokenizer::findGarbageCode() const !Token::Match(tok->previous(), "{|, . %name% =|.|[|{") && !Token::Match(tok->previous(), ", . %name%")) { if (!Token::Match(tok->previous(), "%name%|)|]|>|}")) - syntaxError(tok, tok->strAt(-1) + " " + tok->str() + " " + tok->strAt(1)); + unknownMacroSyntaxError(tok->previous(), "member access on literal", tok, tok->strAt(-1) + " " + tok->str() + " " + tok->strAt(1)); if (!Token::Match(tok->next(), "%name%|*|~")) syntaxError(tok, tok->strAt(-1) + " " + tok->str() + " " + tok->strAt(1)); } diff --git a/lib/tokenize.h b/lib/tokenize.h index 49f622d1f49..b24e5e834cb 100644 --- a/lib/tokenize.h +++ b/lib/tokenize.h @@ -385,6 +385,10 @@ class CPPCHECKLIB Tokenizer { /** Warn about unknown macro(s), configuration is recommended */ NORETURN void unknownMacroError(const Token *tok1) const; + /** Syntax error that may be due to an unknown macro, for example START() where START=1 */ + NORETURN void unknownMacroSyntaxError(const Token *tok, const std::string &description, + const Token *errTok = nullptr, const std::string &code = emptyString) const; + void unhandledCharLiteral(const Token *tok, const std::string& msg) const; private: diff --git a/test/testcppcheck.cpp b/test/testcppcheck.cpp index ad4c6198385..1d2db08a906 100644 --- a/test/testcppcheck.cpp +++ b/test/testcppcheck.cpp @@ -63,6 +63,8 @@ class TestCppcheck : public TestFixture { TEST_CASE(getClangFlagsIncludeFile); TEST_CASE(invalidSyntaxUnknownMacro1); // #13378 TEST_CASE(invalidSyntaxUnknownMacro2); // #13378 + TEST_CASE(invalidSyntaxUnknownMacro3); + TEST_CASE(invalidSyntaxUnknownMacro4); } void getErrorMessages() const { @@ -293,6 +295,46 @@ class TestCppcheck : public TestFixture { ASSERT_EQUALS("syntaxError", errorLogger.errmsgs.cbegin()->id); } + void invalidSyntaxUnknownMacro3() { + ScopedFile file("test.c", + "void foo(void) {\n" + "#ifdef START\n" + " int k = START.f;\n" + "#endif\n" + "}\n"); + ErrorLogger2 errorLogger; + CppCheck cppcheck(errorLogger, false, {}); + + ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(file.path()))); + errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) { + return msg.id == "logChecker"; + }), errorLogger.errmsgs.end()); + ASSERT_EQUALS(1, errorLogger.errmsgs.size()); + ASSERT_EQUALS("member access on literal. Macro 'START' expands to '1'. Use -DSTART=... to specify a value, or -USTART to undefine it.", + errorLogger.errmsgs.cbegin()->shortMessage()); + ASSERT_EQUALS("unknownMacro", errorLogger.errmsgs.cbegin()->id); + } + + void invalidSyntaxUnknownMacro4() { + ScopedFile file("test.c", + "void foo(void) {\n" + "#ifdef START\n" + " int k = START.f;\n" + "#endif\n" + "}\n"); + ErrorLogger2 errorLogger; + CppCheck cppcheck(errorLogger, false, {}); + cppcheck.settings().userDefines = "START=1"; // cppcheck -DSTART ... + + ASSERT_EQUALS(1, cppcheck.check(FileWithDetails(file.path()))); + errorLogger.errmsgs.erase(std::remove_if(errorLogger.errmsgs.begin(), errorLogger.errmsgs.end(), [](const ErrorMessage& msg) { + return msg.id == "logChecker"; + }), errorLogger.errmsgs.end()); + ASSERT_EQUALS(1, errorLogger.errmsgs.size()); + ASSERT_EQUALS("syntax error: 1 . f", errorLogger.errmsgs.cbegin()->shortMessage()); + ASSERT_EQUALS("syntaxError", errorLogger.errmsgs.cbegin()->id); + } + // TODO: test suppressions // TODO: test all with FS };