https://gcc.gnu.org/bugzilla/show_bug.cgi?id=125693

            Bug ID: 125693
           Summary: std::vector::insert(it, cref) crashes when
                    sizeof(value_type) ≥ 8MiB
           Product: gcc
           Version: 15.2.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: marc.mutz at hotmail dot com
  Target Milestone: ---

I found this gem in Qt's test suite:

    { // make sure we don't kill the compiler with recursive templates
        QList<std::array<Object *, 1'000'000>> wideList;
        QRangeModel model(wideList);
    }

so I was wondering how std::vector fares with types whose size exceeds the
platform's stack size.

For the most part, everything works (good!), but insert(it, cref) doesn't,
because it takes a protective copy on the stack. That triggers SIGSEGV due to
stack exhaustion from that single element, of course).

I know that insertion in the middle of a vector with 8MiB-sized elements is
dubious, but one thing is performance, the other is correctness.

Note that emplace(it, args...) somehow gets this right (though `sizeof...(args)
== 0` in my test; emplace(it, val) doesn't).

Here's the test I'm using:

template<typename Container>
void copesWithVeryLargeElementTypes_impl()
{
    using V = typename Container::value_type;
    using S = typename Container::size_type;

    // WARNING: Objects of type V must never be allocated on the stack,
    //          as that would blow the stack up!

    Container c;                       // c == {}
    S n = 0;

#define CHECK_SIZE_INCREASED() do { ++n; assert(c.size() == n); } while (false)
#define CHECK_SIZE_DECREASED() do { --n; assert(c.size() == n); } while (false)

    // default-constructed elements:
    c.emplace_back();                  // c == {1}   at the end
    CHECK_SIZE_INCREASED();
    c.emplace(c.begin());              // c == {21}  at the front
    CHECK_SIZE_INCREASED();
    c.emplace(std::next(c.begin()));   // c == {231} in the middle
    CHECK_SIZE_INCREASED();

    // copies (lvalue):
    {
        auto p = std::make_unique<V>();
        c.push_back(*p);               // c == {2314}
        CHECK_SIZE_INCREASED();
        if constexpr (false) { // stack exhaustion on these two operations
        c.emplace(c.begin(), *p);       // c == {52314}
        CHECK_SIZE_INCREASED();
        c.insert(std::next(c.begin(), 2), *p); // c == {526314}
        CHECK_SIZE_INCREASED();
        }
    }

    // erasure
    c.erase(std::next(c.begin()));     // c == {56314}  in the middle
    CHECK_SIZE_DECREASED();
    c.erase(c.begin());                // c == {6314}   at the front
    CHECK_SIZE_DECREASED();
    c.pop_back();                      // c == {631}    at the end
    CHECK_SIZE_DECREASED();

    // moves (rvalues)
    const auto rvalue = [](std::unique_ptr<V> &&storage = nullptr)
        -> V&& // don't return by value!
    {
        // std::unique_ptr::op* isn't overloaded on rvalue-this:
        storage = std::make_unique<V>();
        return std::move(*storage);
    };

    c.push_back(rvalue());                       // c == {6317}
    CHECK_SIZE_INCREASED();
    c.emplace(c.begin(), rvalue());              // c == {86317}
    CHECK_SIZE_INCREASED();
    c.insert(std::next(c.begin(), 2), rvalue()); // c == {869317}
    CHECK_SIZE_INCREASED();

    // and, finally, clear():
    c.clear();                         // c == {}
    QCOMPARE(c.size(), S{0});

#undef CHECK_SIZE_DECREASED
#undef CHECK_SIZE_INCREASED
}

using LargerThanStack = std::array<std::byte, 8 * 1024 * 1024>;

int main() {
    copesWithVeryLargeElementTypes_impl<std::vector<LargerThanStack>();
}

Reply via email to