Title
In lazy_split_view, comparing a default-constructed outer-iterator or inner-iterator with std::default_sentinel results in null pointer dereference
Status
new
Section
[range.lazy.split.outer][range.lazy.split.inner]
Submitter
Konstantin Varlamov

Created on 2022-03-23.00:00:00 last changed 30 months ago

Messages

Date: 2022-05-17.11:58:16

Proposed resolution:

This wording is relative to N4910.

  1. Modify [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 [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_};
    […]
    
Date: 2022-05-15.00:00:00

[ 2022-05-17; Reflector poll ]

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

Date: 2022-03-29.15:56:11

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 ([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 ([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.

History
Date User Action Args
2022-05-17 11:58:16adminsetmessages: + msg12460
2022-03-26 12:51:20adminsetmessages: + msg12413
2022-03-23 00:00:00admincreate