On Mon, 24 Mar 2025 at 12:40, Tomasz Kaminski wrote:
> As a note reserving for empty vector:
> +             // If there are no existing elements it's safe to allocate now.
> +             if (__sz == 0)
> +               reserve(__n);
> Will invalidate v.begin(), v.end(), so if the incoming range stored these
> iterators as a range, and uses it internally, this will be broken.
> If these iterator are saved before the call, this does not run into 
> re-entrancy.
> We could change that to look if capacity is 0 (to optimize default_construct
> + append case), but I am fine with not supporting this case.


Good catch, e.g. this would have undefined behaviour:

#define _GLIBCXX_DEBUG
#include <vector>
#include <ranges>
int main()
{
  std::vector<int> v1(1);
  std::vector<int> v2(20);
  v1.clear();
  std::ranges::subrange r1(v1.begin(), v1.end());
  std::ranges::subrange r2(v2.begin(), v2.end());
  v1.append_range(std::views::concat(r2, r1));
}

r1 contains valid iterators to the empty v1, then append_range does
reserve(20) and invalidates r1.

It doesn't fail in debug mode because
__gnu_debug::vector::append_range doesn't invalidate all existing
iterators until *after* calling Base::append_range, so when the
reserve(20) call invalidates r1, the debug iterators inside r1 don't
get invalidated immediately. So when we iterate over the concat_view,
r1.begin()==r1.end() and we don't notice we're using invalid
iterators.

And it doesn't fail for constexpr either, because of PR119426. So I
don't currently have a test that fails due to the bug noted above.
I'll fix it, and maybe try to improve
__gnu_debug::vector::append_range to invalidate iterators before
calling Base::append_range.

Reply via email to