Skip to content

CHECK_EQUAL: fix array != array compare under C++26 - #1886

Open
dale-stewart wants to merge 2 commits into
cpputest:masterfrom
dale-stewart:fix/check-equal-cpp26-array-comparison
Open

CHECK_EQUAL: fix array != array compare under C++26#1886
dale-stewart wants to merge 2 commits into
cpputest:masterfrom
dale-stewart:fix/check-equal-cpp26-array-comparison

Conversation

@dale-stewart

Copy link
Copy Markdown

Problem

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 commonly 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. CppUTest's existing tests don't exercise the array-operand path, so this never showed up in CI; it surfaces immediately in any consuming project that does.

Fix

A small CppUTestPrivate::checkEqualSelfNotEqual<T>(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)) route 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).

Test

Added CHECK_EQUAL_compilesWithArrayOperand in TestUTestMacro.cpp. Without the fix, the test TU emits -Wdeprecated-array-compare under C++20+ with -Werror, and is ill-formed at C++26 regardless of warning flags. With the fix it compiles at every standard from C++98 through C++26.

CI coverage

Verified by the existing -Wall -Werror jobs (Make Defaults, CMake Linux GNU 17/20/23, etc.) — -Wdeprecated-array-compare is on by default at C++20+ and gates the relevant condition. A future PR could add a C++26 job once compiler C++26 support stabilizes; this fix doesn't depend on that addition.

Compatibility

Works at C++98 through C++26. No new headers included. The helper is a plain function template — no <type_traits>, no if constexpr, no forwarding refs.

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, 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.
@basvodde

Copy link
Copy Markdown
Member

Thanks for the PR. Since in CppUTest we don't use templates, I'll need to look for an alternative solution. But, I verified the problem.

@basvodde

Copy link
Copy Markdown
Member

I've checked this further. As we will not add templated code to CppUTest, this means there is no fix for this (afaik). However, the issue can be fixed relative easily by adding a cast in the call. So, I'm leaning towards just fixing this in the call towards CHECK_EQUAL and not in CppUTest itself. This should be easy as it gives a very explicit compiler error.

Will that be ok with you? Thanks for the PR anyways...

ps. I did added C++26 support in the build so we can find incompatibilities as early as possible.

1 similar comment
@basvodde

Copy link
Copy Markdown
Member

I've checked this further. As we will not add templated code to CppUTest, this means there is no fix for this (afaik). However, the issue can be fixed relative easily by adding a cast in the call. So, I'm leaning towards just fixing this in the call towards CHECK_EQUAL and not in CppUTest itself. This should be easy as it gives a very explicit compiler error.

Will that be ok with you? Thanks for the PR anyways...

ps. I did added C++26 support in the build so we can find incompatibilities as early as possible.

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) <noreply@anthropic.com>
@dale-stewart

Copy link
Copy Markdown
Author

Good catch.

Replaced it with a StringFrom-based probe, no template:

if (StringFrom(actual) != StringFrom(actual))

Arrays decay to pointers at the call boundary, so no array-to-array comparison is formed, and StringFrom is already a documented requirement for CHECK_EQUAL operands. Net diff is +2/-2. Rationale, the two trade-offs, and the verification runs are in the commit message.

Verified at C++98/23/26 with -Wall -Wextra -Werror -- clean build, suite green.

@dale-stewart

Copy link
Copy Markdown
Author

I just noticed your second reply. I've offered a partial solution, do with it whatever you like. I no longer have the need I originally had for this change.

@basvodde

Copy link
Copy Markdown
Member

The old solution uses the operator != and it is documented to expect that. Doing the comparison with StringFrom breaks that, hence I do not prefer that (also it will make it a lot slower). I'll be not accepting this PR and instead expect it to be fixed at the call rather than in CppUTest

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants