Issue 141499
Summary libc++: Several related issues in std::layout_* with empty extents.
Labels libc++
Assignees
Reporter 1uc
    Summary: The layouts in <mdspan> suffer from several issues when used with empty extents.

Context: There's a mandate that requires that required_span_size is representable [[0]]; and if the mandate doesn't apply the same condition is asserted as a precondition in every ctor, e.g. [[1]]. For non-empty extents this effectively prevents overflow when computing `stride` or `required_span_size`. However, if one of the extents is `0` either statically or dynamically, the mandate doesn't protect from overflow.

[0]: https://eel.is/c++draft/views.multidim#mdspan.layout.right.overview-4
[1]: https://eel.is/c++draft/views.multidim#mdspan.layout.right.cons-1

Godbolt: https://godbolt.org/z/oqaMnYT74

Example 1:
```
template<typename Int>
constexpr bool
late_static_zero_overflow()
{
    constexpr auto n = std::numeric_limits<Int>::max();
    // Error: extents size not prepresentable as index_type.
    constexpr auto m = std::layout_right::mapping(std::extents<Int, n, n, 0>{});
    return true;
}
static_assert(late_static_zero_overflow<uint16_t>());
```
I believe that this is valid code as it doesn't violate any mandate or precondition, yet it's rejected. (This example doesn't exhibit any overflow.)

Example 2:
```
template<typename Int>
constexpr bool
late_dynamic_zero_overflow()
{
    constexpr auto n = std::numeric_limits<Int>::max();
    constexpr auto m = std::layout_right::mapping(std::extents<Int, n, n, dyn>{0});
    // Error: causes overflow
    static_assert(m.required_span_size() == 0);
    return true;
}
static_assert(late_dynamic_zero_overflow<uint16_t>());
```
This causes an overflow because as an intermediate value it computes `n * n`, due to integer promotion rules, this is computed as `int(n) * int(n)` which (assuming `int` is a 32-bit integer) causes a signed overflow, i.e. UB. (Later it would multiply by `0`, but that happens too late.)

Example 3:
```
template<typename Int>
constexpr bool
stride_overflow()
{
 constexpr auto n = std::numeric_limits<Int>::max();
    constexpr auto m = std::layout_right::mapping(std::extents<Int, 0, n, n, n>{});
 static_assert(m.required_span_size() == 0);

    // Error: causes overflow
    static_assert(m.stride(1) != 0);
    return true;
}
static_assert(stride_overflow<uint16_t>());
```
This computes `n * n` as `int(n) * int(n)` which causes a signed overflow which is UB. The relevant section in the standard seem to be:
https://eel.is/c++draft/mdspan.layout#reqmts-18
https://eel.is/c++draft/views.multidim#mdspan.layout.left.obs-7
https://eel.is/c++draft/views.multidim#mdspan.extents.expo-6

Note that the layout requirements define the strides at any number such that `m(i...) = (s[i]*i + ... + 0)` . For and empty `std::extents`, this is satisfied trivially, i.e. any value of `s[i]` satisfied the condition. The definition of `layout_left::mapping::stride` requires that it's a forward product. Note that the exposition helper itself returns `size_t` not `index_type` suggesting that the forward product is computed in `size_t` (which is indeed one solution to avoid the UB, naturally the overflow still occurs).
 
Example 4:
```
template<typename Int>
constexpr bool
layout_stride_default_ctor()
{
    constexpr auto n = std::numeric_limits<Int>::max();
    // Error: not initialized by a constant, due to overflow while
    // computing _M_strides.
    constexpr auto m = std::layout_stride::mapping<std::extents<Int, 0, n, n, n>>{};
 return true;
}
static_assert(layout_stride_default_ctor<uint16_t>());
```
Here an overflow occurs while computing the strides as part of the default ctor.

I've only looked at C++23 code and not checked `std::layout_padded_{left,right}`.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to