From d53c542ba003a92f99f92621dac0a8718b50b1e7 Mon Sep 17 00:00:00 2001 From: tzi4 Date: Tue, 8 Sep 2026 00:44:07 +0300 Subject: [PATCH 1/3] 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 e2e19b6bd91..f2b2e415e65 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1610,6 +1610,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 61ff7b2dae5..c8372612e93 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -12374,6 +12374,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 dccbfe5ed8a053563f5c4ff2edbabffd0e917620 Mon Sep 17 00:00:00 2001 From: tzi4 Date: Tue, 8 Sep 2026 16:51:12 +0300 Subject: [PATCH 2/3] 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 f2b2e415e65..25a2794fadb 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1611,10 +1611,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 c8372612e93..cb9f4fef88f 100644 --- a/test/testother.cpp +++ b/test/testother.cpp @@ -12381,6 +12381,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() { From f4d577a92ae3c9480c6d187479eecf90c9d27b18 Mon Sep 17 00:00:00 2001 From: tzi4 Date: Sun, 11 Oct 2026 01:53:06 +0300 Subject: [PATCH 3/3] Use direct token checks for string prefixes --- lib/astutils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/astutils.cpp b/lib/astutils.cpp index 25a2794fadb..867945afa2f 100644 --- a/lib/astutils.cpp +++ b/lib/astutils.cpp @@ -1612,8 +1612,8 @@ bool isSameExpression(bool macro, const Token *tok1, const Token *tok2, const Se return false; // An unknown string-prefix macro leaves the literal outside the AST. // 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%"); + const bool stringPrefix1 = tok1->isName() && !tok1->isKeyword() && tok1->next() && tok1->next()->tokType() == Token::eString; + const bool stringPrefix2 = tok2->isName() && !tok2->isKeyword() && tok2->next() && tok2->next()->tokType() == Token::eString; if (stringPrefix1 || stringPrefix2) { if (!stringPrefix1 || !stringPrefix2) return false;