From 475789b90370bd418455b77ea449ea3d095076be Mon Sep 17 00:00:00 2001 From: tzi4 Date: Tue, 8 Sep 2026 00:44:07 +0300 Subject: [PATCH 1/2] Fix false duplicate warnings with string prefix macros --- lib/astutils.cpp | 5 +++++ test/testastutils.cpp | 6 ++++++ test/testother.cpp | 7 +++++++ 3 files changed, 18 insertions(+) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 1faafd4ba09..86732c93b16 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1607,6 +1607,11 @@ bool isSameExpression(bool macro, const Token *tok1, const Token *tok2, const Se return true; if (tok1 == nullptr || tok2 == nullptr) return false; + // An unknown string-prefix macro leaves the literal outside the AST. + // Comparing only the macro name would ignore the rest of the expression. + if ((!tok1->isKeyword() && Token::Match(tok1, "%name% %str%")) || + (!tok2->isKeyword() && Token::Match(tok2, "%name% %str%"))) + return false; // tokens needs to be from the same TokenList so no need check standard on both of them if (tok1->isCpp()) { if (tok1->str() == "." && tok1->astOperand1() && tok1->astOperand1()->str() == "this") diff --git a/test/testastutils.cpp b/test/testastutils.cpp index 46dde522338..dbc60ab0864 100644 --- a/test/testastutils.cpp +++ b/test/testastutils.cpp @@ -198,11 +198,17 @@ class TestAstUtils : public TestFixture { ASSERT_EQUALS(!cpp, isSameExpression("void f() {double y = 1e1; (x + 10.0) < (y + x); } \n", "+", "+", cpp)); ASSERT_EQUALS(true, isSameExpression("void f() {double y = 1e1; double z = 10.0; (x + y) < (x + z); } \n", "+", "+", cpp)); ASSERT_EQUALS(true, isSameExpression("A + A\n", "A", "A", cpp)); + // An unknown string-prefix macro leaves the literal outside the AST. #5738 + ASSERT_EQUALS(false, isSameExpression("x == PREFIX \"/a\" || x == PREFIX \"/b\";\n", "==", "==", cpp)); + ASSERT_EQUALS(false, isSameExpression("x == PREFIX \"/a\" || x == PREFIX;\n", "==", "==", cpp)); + ASSERT_EQUALS(false, isSameExpression("x == PREFIX || x == PREFIX \"/a\";\n", "==", "==", cpp)); // the remaining test cases are not valid C code if (!cpp) return; + ASSERT_EQUALS(true, isSameExpression("void f(int x) { x ? throw \"a\" : throw \"a\"; }\n", "throw", "throw", cpp)); + //https://trac.cppcheck.net/ticket/9700 ASSERT_EQUALS(true, isSameExpression("A::B + A::B;\n", "::", "::", cpp)); ASSERT_EQUALS(false, isSameExpression("A::B + A::C;\n", "::", "::", cpp)); diff --git a/test/testother.cpp b/test/testother.cpp index 27f7700bd54..a6ecad10727 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -12163,6 +12163,13 @@ class TestOther : public TestFixture { " return name.startswith(SRCDIR \"/com/\") || name.startswith(SRCDIR \"/uno/\");\n" "};\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); + + check("bool isInUnoIncludeFile(StringRef name) {\n" + " return isInMainFile()\n" + " ? (name == SRCDIR \"/cppu/compat.cxx\" || name == SRCDIR \"/sal/compat.cxx\")\n" + " : (name.startswith(SRCDIR \"/com/\") || name.startswith(SRCDIR \"/uno/\"));\n" + "}\n", dinit(CheckOptions, $.inconclusive = false)); + ASSERT_EQUALS("", errout_str()); } void raceAfterInterlockedDecrement() { From 2a0c507aec8bbcc8f9f67f2ced881e082de37a3b Mon Sep 17 00:00:00 2001 From: tzi4 Date: Tue, 8 Sep 2026 16:51:12 +0300 Subject: [PATCH 2/2] Preserve duplicate warnings for matching string prefix literals --- lib/astutils.cpp | 18 ++++++++++++++---- test/testastutils.cpp | 5 +++++ test/testother.cpp | 5 +++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 86732c93b16..d1db0ebaa94 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1608,10 +1608,20 @@ bool isSameExpression(bool macro, const Token *tok1, const Token *tok2, const Se if (tok1 == nullptr || tok2 == nullptr) return false; // An unknown string-prefix macro leaves the literal outside the AST. - // Comparing only the macro name would ignore the rest of the expression. - if ((!tok1->isKeyword() && Token::Match(tok1, "%name% %str%")) || - (!tok2->isKeyword() && Token::Match(tok2, "%name% %str%"))) - return false; + // Compare the literal text as well so identical literals still compare equal. + const bool stringPrefix1 = !tok1->isKeyword() && Token::Match(tok1, "%name% %str%"); + const bool stringPrefix2 = !tok2->isKeyword() && Token::Match(tok2, "%name% %str%"); + if (stringPrefix1 || stringPrefix2) { + if (!stringPrefix1 || !stringPrefix2) + return false; + for (const Token* str1 = tok1->next(), *str2 = tok2->next(); + Token::Match(str1, "%str%") || Token::Match(str2, "%str%"); + str1 = str1->next(), str2 = str2->next()) { + if (!Token::Match(str1, "%str%") || !Token::Match(str2, "%str%") || + str1->str() != str2->str() || !compareTokenFlags(str1, str2, macro)) + return false; + } + } // tokens needs to be from the same TokenList so no need check standard on both of them if (tok1->isCpp()) { if (tok1->str() == "." && tok1->astOperand1() && tok1->astOperand1()->str() == "this") diff --git a/test/testastutils.cpp b/test/testastutils.cpp index dbc60ab0864..e10505abcdd 100644 --- a/test/testastutils.cpp +++ b/test/testastutils.cpp @@ -202,6 +202,11 @@ class TestAstUtils : public TestFixture { ASSERT_EQUALS(false, isSameExpression("x == PREFIX \"/a\" || x == PREFIX \"/b\";\n", "==", "==", cpp)); ASSERT_EQUALS(false, isSameExpression("x == PREFIX \"/a\" || x == PREFIX;\n", "==", "==", cpp)); ASSERT_EQUALS(false, isSameExpression("x == PREFIX || x == PREFIX \"/a\";\n", "==", "==", cpp)); + ASSERT_EQUALS(true, isSameExpression("x == PREFIX \"/a\" || x == PREFIX \"/a\";\n", "==", "==", cpp)); + ASSERT_EQUALS(true, isSameExpression("x == PREFIX \"/a\" \"/b\" || x == PREFIX \"/a\" \"/b\";\n", "==", "==", cpp)); + ASSERT_EQUALS(false, isSameExpression("x == PREFIX \"/a\" \"/b\" || x == PREFIX \"/a\" \"/c\";\n", "==", "==", cpp)); + ASSERT_EQUALS(false, isSameExpression("x == FIRST \"/a\" || x == SECOND \"/a\";\n", "==", "==", cpp)); + ASSERT_EQUALS(false, isSameExpression("x == PREFIX L\"/a\" || x == PREFIX \"/a\";\n", "==", "==", cpp)); // the remaining test cases are not valid C code if (!cpp) diff --git a/test/testother.cpp b/test/testother.cpp index a6ecad10727..f99dce7cd14 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -12170,6 +12170,11 @@ class TestOther : public TestFixture { " : (name.startswith(SRCDIR \"/com/\") || name.startswith(SRCDIR \"/uno/\"));\n" "}\n", dinit(CheckOptions, $.inconclusive = false)); ASSERT_EQUALS("", errout_str()); + + check("bool f(StringRef name) {\n" + " return name == SRCDIR \"/a\" || name == SRCDIR \"/a\";\n" + "}\n", dinit(CheckOptions, $.inconclusive = false)); + ASSERT_EQUALS("[test.cpp:2:32]: (style) Same expression on both sides of '||'. [duplicateExpression]\n", errout_str()); } void raceAfterInterlockedDecrement() {