3686. In lazy_split_view, comparing a default-constructed outer-iterator or inner-iterator with std::default_sentinel results in null pointer dereference

Section: 25.7.16.3 [range.lazy.split.outer], 25.7.16.5 [range.lazy.split.inner] Status: New Submitter: Konstantin Varlamov Opened: 2022-03-23 Last modified: 2022-05-17

Priority: 3

View all other issues in [range.lazy.split.outer].

View all issues with New status.

Discussion:

The internal iterator types outer-iterator and inner-iterator of lazy_split_view are default-constructible, but trying to compare a default-constructed instance of either of these classes to std::default_sentinel results in null pointer dereference (and, in all likelihood, a crash), as demonstrated in this demo link:

// Assuming OuterIter is an alias for outer-iterator of
// some lazy_split_view instantiation.
OuterIter o;
o == std::default_sentinel; // Null pointer dereference

InnerIter i; // Similar to OuterIter above.
i == std::default_sentinel; // Null pointer dereference

This is due to unchecked pointer access in the implementation of outer-iterator (25.7.16.3 [range.lazy.split.outer] p8):

return x.current == ranges::end(x.parent_->base_) && !x.trailing_empty_;

(parent_ is null for a default-constructed iterator x, making the access to base_ invalid)

And similarly for inner-iterator (25.7.16.5 [range.lazy.split.inner] p7):

auto [pcur, pend] = subrange{x.i_.parent_->pattern_};

(For a default-constructed inner-iterator x, i_ is a default-constructed outer-iterator member variable and i_.parent_ is null, making the access to pattern_ invalid)

It seems a reasonable expectation for users to expect comparing a default-constructed iterator to std::default_sentinel to be a well-defined operation that returns true. Alternatively, the corresponding operator== functions should add a non-normative note stating that the iterator cannot be default-constructed.

[2022-05-17; Reflector poll]

Set priority to 3 after reflector poll. Three votes for NAD.

Proposed resolution:

This wording is relative to N4910.

  1. Modify 25.7.16.3 [range.lazy.split.outer] as indicated:

    friend constexpr bool operator==(const outer-iterator& x, default_sentinel_t);
    

    -8- Effects: Equivalent to:

    if (!x.parent_) return true;
    return x.current == ranges::end(x.parent_->base_) && !x.trailing_empty_;
    
  2. Modify 25.7.16.5 [range.lazy.split.inner], as indicated:

    friend constexpr bool operator==(const inner-iterator& x, default_sentinel_t);
    

    -7- Effects: Equivalent to:

    if (!x.i_.parent_) return true;
    auto [pcur, pend] = subrange{x.i_.parent_->pattern_};
    […]