From 7903b781cfda1279cd88ecc9ebb68882142acf17 Mon Sep 17 00:00:00 2001 From: Dale Stewart Date: Sat, 9 May 2026 10:41:29 -0500 Subject: [PATCH 1/2] CHECK_EQUAL_LOCATION: route self-compare probes through a helper C++26 [over.match.oper]/p10 makes array == array (and array != array) ill-formed. CHECK_EQUAL_LOCATION expanded its two side-effect-detection probes literally as (operand) != (operand). As soon as either operand was an array (most often a string literal in CHECK_EQUAL("text", x)), the inner self-compare became a forbidden array-vs-array compare. The same pattern is also a -Wdeprecated-array-compare error under C++20+ when -Werror is on. Add a CppUTestPrivate::checkEqualSelfNotEqual(T, T) helper that takes both arguments by value, so any array decays to a pointer before the comparison runs. The two side-effect probes ((actual) != (actual) and (expected) != (expected)) are routed through it. Both arguments come from the same operand expression, so T is identical on both sides; -Wsign-compare cannot fire. The outer (expected) != (actual) is intentionally left at the macro call site. Routing it through a function template would lose -Wsign-compare's constant-fits exception that lets idioms like CHECK_EQUAL(0, some_unsigned) build clean today. Behavior is unchanged for every non-array operand. For arrays, the resulting pointer-address self-compare matches the historical C++03 behavior (always false, since the same array's decayed pointer equals itself). Add a regression test exercising the array-operand path. Without the helper, the test TU is ill-formed under C++26 and emits -Wdeprecated-array-compare under C++20+; with the helper it compiles at every standard from C++98 through C++26. --- include/CppUTest/UtestMacros.h | 13 +++++++++++-- tests/CppUTest/TestUTestMacro.cpp | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/include/CppUTest/UtestMacros.h b/include/CppUTest/UtestMacros.h index 743bf069e..a90c0e9bf 100644 --- a/include/CppUTest/UtestMacros.h +++ b/include/CppUTest/UtestMacros.h @@ -129,11 +129,20 @@ #define CHECK_EQUAL_TEXT(expected, actual, text)\ CHECK_EQUAL_LOCATION(expected, actual, text, __FILE__, __LINE__) +namespace CppUTestPrivate +{ + template + bool checkEqualSelfNotEqual(T a, T b) + { + return a != b; + } +} + #define CHECK_EQUAL_LOCATION(expected, actual, text, file, line)\ do { if ((expected) != (actual)) { \ - if ((actual) != (actual)) \ + if (::CppUTestPrivate::checkEqualSelfNotEqual((actual), (actual))) \ UtestShell::getCurrent()->print("WARNING:\n\tThe \"Actual Parameter\" parameter is evaluated multiple times resulting in different values.\n\tThus the value in the error message is probably incorrect.", file, line); \ - if ((expected) != (expected)) \ + if (::CppUTestPrivate::checkEqualSelfNotEqual((expected), (expected))) \ UtestShell::getCurrent()->print("WARNING:\n\tThe \"Expected Parameter\" parameter is evaluated multiple times resulting in different values.\n\tThus the value in the error message is probably incorrect.", file, line); \ UtestShell::getCurrent()->assertEquals(true, StringFrom(expected).asCharString(), StringFrom(actual).asCharString(), text, file, line); \ } \ diff --git a/tests/CppUTest/TestUTestMacro.cpp b/tests/CppUTest/TestUTestMacro.cpp index ecc07a619..b6ce1f8a2 100644 --- a/tests/CppUTest/TestUTestMacro.cpp +++ b/tests/CppUTest/TestUTestMacro.cpp @@ -489,6 +489,13 @@ TEST(UnitTestMacros, failing_CHECK_EQUAL_withParamatersThatDontChangeWillNotGive fixture.assertPrintContainsNot("WARNING"); } +TEST(UnitTestMacros, CHECK_EQUAL_compilesWithArrayOperand) +{ + const char buf[] = "hello"; + const char* actual = buf; + CHECK_EQUAL(buf, actual); +} + TEST(UnitTestMacros, CHECK_EQUALBehavesAsProperMacro) { if (false) CHECK_EQUAL(1, 2); From 39b87892f21ab46dc4912e9fcc600091b351e924 Mon Sep 17 00:00:00 2001 From: Dale Stewart Date: Thu, 30 Jul 2026 08:38:43 -0500 Subject: [PATCH 2/2] CHECK_EQUAL_LOCATION: use StringFrom for the self-compare probes Replace the function template added in the previous commit with a StringFrom-based probe, per review feedback: CppUTest uses no templates anywhere in include/ or src/, and the helper was the sole exception. The two side-effect-detection probes now compare the string renderings of two separate evaluations of the operand: if (StringFrom(actual) != StringFrom(actual)) if (StringFrom(expected) != StringFrom(expected)) Arrays decay to pointers at the call boundary, so no array-to-array comparison is ever formed. This imposes no new requirement on operand types: CHECK_EQUAL already documents that it "needs the operator!=(), and a StringFrom(YourType) function", and the failure path already calls StringFrom on both operands to build the message. The operand is still evaluated twice, so multiple-evaluation detection is preserved. The detection now compares string renderings rather than values, so an operand whose two evaluations differ only beyond StringFrom's default precision (e.g. a double past 6 digits) no longer warns. That only suppresses an advisory message on an assertion that is already failing. The outer (expected) != (actual) remains at the macro call site, so CHECK_EQUAL with two array operands is still ill-formed under C++26. That case is user code writing a deprecated comparison directly; the defect being fixed is CppUTest synthesizing one the caller never wrote. Verified at C++98/23 (g++-13), C++26 (g++-14, g++-16), all with -Wall -Wextra -Werror: builds clean, full suite passes. Restoring the original macro fails as expected -- -Werror=array-compare on g++-13 at C++20+, and "not allowed in C++26" on g++-16. Note for reproducing the g++-16 run: it additionally requires -Wno-keyword-macro, because g++-16 rejects MemoryLeakDetectorNewMacros.h:97 with "keyword 'new' defined as macro". That is pre-existing and unrelated to this change, but it will block any future C++26 CI job. Co-Authored-By: Claude Opus 5 (1M context) --- include/CppUTest/UtestMacros.h | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/include/CppUTest/UtestMacros.h b/include/CppUTest/UtestMacros.h index a90c0e9bf..b266aa3f7 100644 --- a/include/CppUTest/UtestMacros.h +++ b/include/CppUTest/UtestMacros.h @@ -129,20 +129,11 @@ #define CHECK_EQUAL_TEXT(expected, actual, text)\ CHECK_EQUAL_LOCATION(expected, actual, text, __FILE__, __LINE__) -namespace CppUTestPrivate -{ - template - bool checkEqualSelfNotEqual(T a, T b) - { - return a != b; - } -} - #define CHECK_EQUAL_LOCATION(expected, actual, text, file, line)\ do { if ((expected) != (actual)) { \ - if (::CppUTestPrivate::checkEqualSelfNotEqual((actual), (actual))) \ + if (StringFrom(actual) != StringFrom(actual)) \ UtestShell::getCurrent()->print("WARNING:\n\tThe \"Actual Parameter\" parameter is evaluated multiple times resulting in different values.\n\tThus the value in the error message is probably incorrect.", file, line); \ - if (::CppUTestPrivate::checkEqualSelfNotEqual((expected), (expected))) \ + if (StringFrom(expected) != StringFrom(expected)) \ UtestShell::getCurrent()->print("WARNING:\n\tThe \"Expected Parameter\" parameter is evaluated multiple times resulting in different values.\n\tThus the value in the error message is probably incorrect.", file, line); \ UtestShell::getCurrent()->assertEquals(true, StringFrom(expected).asCharString(), StringFrom(actual).asCharString(), text, file, line); \ } \