Boost.GIL test failing with clang 5.x while pass with 15 gcc/clang versions

Hi,

While testing Boost.GIL library [1] with gcc and clang, I noticed a
peculiar issue.
One particular test is failing with lang 5.x while passing with total of 15-17
other versions of clang and GCC (total workflow of CircleCI with at [2]).

Below is extracted minimal program equivalent to Boost.GIL
channel_invert algorithm. It includes two variants: plain expression
and the same expression wrapped with a function template:

#include <limits>
#include <iostream>
#include <typeinfo>

template <typename C>
inline C channel_invert1(C x)
{
    return std::numeric_limits<C>::max() - x + std::numeric_limits<C>::min();
}

template <typename C>
inline C channel_invert2(C x)
{
    return (x - std::numeric_limits<C>::max()) * (-1) +
std::numeric_limits<C>::min();
}

int main()
{
    int x = std::numeric_limits<int>::min();
    std::cout << x << std::endl;

    // plain expressions
    int x_invert1 = std::numeric_limits<int>::max() - x +
std::numeric_limits<int>::min();
    int x_invert2 = (x - std::numeric_limits<int>::max()) * (-1) +
std::numeric_limits<int>::min();
    std::cout << x_invert1 << std::endl;
    std::cout << x_invert2 << std::endl;

    // the same expressions wrapped in function template
    std::cout << channel_invert1<int>(x) << std::endl;
    std::cout << channel_invert2<int>(x) << std::endl;
}

If compiled as optimised variant (-O2 or -O3) with clang 5.x outputs
the following:

-2147483648
2147483647
2147483647
-1
-1

The last two negative one is not expected.

If compiled with clang 3.9, 4.0 or gcc from 5.1 to 7.3 it outputs

-2147483648
2147483647
2147483647
2147483647
2147483647

Could anyone help me to understand what is going on in the clang 5 case?
Or, what UB is this hitting?

[1] Test of channel_invert<int> failing with clang 5.x and variant=release · Issue #89 · boostorg/gil · GitHub
[2] https://circleci.com/workflow-run/3a14dd64-6c38-46b2-a6da-678c0075ca27

Best regards,

Hi Mateusz,

You are hitting UB because of signed integer overflow.

ISO/IEC 14882:2011 clause 5 paragraph 4 and ISO/IEC 14882:2017 clause 8 paragraph 4

“If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined. [...]"

With the exception of unsigned integers:

ISO/IEC 14882:2011 clause 3.9.1 footnote 46 and ISO/IEC 14882:2017 clause 6.9.1 footnote 49
"This implies that unsigned arithmetic does not overflow because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting unsigned integer type."

This code

int x = std::numeric_limits<int>::min();

template <typename C>
inline C channel_invert1(C x)
{
   return std::numeric_limits<C>::max() - x + std::numeric_limits<C>::min();
}

contains (left-to-right operator precedence):

numeric_limits<int>::max() - numeric_limits<int>::min() ...

Where min() is < 0 so it's an overflow.

This code:

int x = std::numeric_limits<int>::min();

template <typename C>
inline C channel_invert2(C x)
{
   return (x - std::numeric_limits<C>::max()) * (-1) +
std::numeric_limits<C>::min();
}

contains

numeric_limits<int>::min() - numeric_limits<int>::max()

Where max() is > 0 so it's an underflow.

BTW This is a perfect opportunity to try out UndefinedBehaviorSanitizer!

https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html

clang++ -fsanitize=signed-integer-overflow overflow.cpp
./a.out

-2147483648
overflow.cpp:24:52: runtime error: signed integer overflow: 2147483647 - -2147483648 cannot be represented in type 'int'
...

Hope this helps.

Jan

Hi Jan,

This is very helpful indeed.

Best regards,

Jan, thank you very much!

Best regards,